Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 0 additions & 36 deletions .pre-commit-config.yaml

This file was deleted.

45 changes: 38 additions & 7 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# Itential Python SDK
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Itential Python SDK

## Development Commands

This project uses `uv` as the Python package manager and build tool. Key commands:
Expand Down Expand Up @@ -34,7 +36,7 @@ The Itential Python SDK provides HTTP client implementations for connecting to I
### Project Details

- **License**: GPL-3.0-or-later
- **Python Support**: >=3.8 (tested on 3.10, 3.11, 3.12)
- **Python Support**: >=3.8 (tested on 3.10, 3.11, 3.12, 3.13)
- **Status**: Beta
- **Primary Dependency**: httpx>=0.28.1
- **Build System**: Hatchling with uv-dynamic-versioning
Expand Down Expand Up @@ -69,7 +71,7 @@ The Itential Python SDK provides HTTP client implementations for connecting to I
- `exceptions.py` - Centralized exception classes for error handling
- `gateway.py` - Itential Automation Gateway client with basic auth
- `jsonutils.py` - JSON serialization/deserialization utilities
- `logger.py` - Logging configuration and utilities with file logging support
- `logging.py` - Comprehensive logging system with file/console handlers, custom FATAL level (90), and httpx/httpcore control
- `metadata.py` - Package metadata and dynamic version information from importlib
- `platform.py` - Itential Platform client with OAuth and basic auth support

Expand All @@ -78,7 +80,13 @@ The Itential Python SDK provides HTTP client implementations for connecting to I
- Automatic authentication on first API call
- Support for both sync and async operations (controlled by `want_async` parameter)
- Configurable TLS, certificate verification, timeouts
- Centralized logging via `logger.set_level()` function with file logging support
- **Comprehensive Logging System**:
- Multiple log levels including custom FATAL (90) level
- Convenience functions: `debug()`, `info()`, `warning()`, `error()`, `critical()`, `fatal()`, `exception()`
- File logging with automatic directory creation and custom formatting
- Console output control (stdout/stderr switching)
- httpx/httpcore logging control via `propagate` parameter
- Centralized configuration via `set_level()` and `configure_file_logging()`
- JSON request/response handling with automatic Content-Type headers


Expand All @@ -88,7 +96,7 @@ The Itential Python SDK provides HTTP client implementations for connecting to I
- Includes 30+ rule sets: pycodestyle (E,W), Pyflakes (F), pyupgrade (UP), flake8-bugbear (B), isort (I), pylint (PL), security checks (S), annotations (ANN), async (ASYNC), and many more
- Line length set to 88 characters (Black-compatible)
- Target Python 3.8+ compatibility
- Per-file ignores configured for different modules (tests/, connection.py, platform.py, gateway.py, logger.py, exceptions.py)
- Per-file ignores configured for different modules (tests/, connection.py, platform.py, gateway.py, logging.py, exceptions.py)
- Auto-fixable rules enabled for most issues
- Double quotes for strings, space indentation, magic trailing comma support
- **Pre-commit Hooks**: Configured in `.pre-commit-config.yaml` for automatic code quality checks
Expand All @@ -110,12 +118,25 @@ Core dev dependencies in `dependency-groups.dev`:
### Testing

- Uses pytest with async support (`pytest-asyncio`)
- Test files in `tests/` directory cover all main components: connection, exceptions, gateway, jsonutils, logger, platform
- Test files in `tests/` directory cover all main components: connection, exceptions, gateway, jsonutils, logging, platform
- Coverage reporting available via pytest-cov with HTML and terminal output
- **Coverage requirement**: Minimum 95% test coverage enforced in CI/CD pipeline
- Tests include extensive per-file ignore rules in ruff config to allow test-specific patterns
- The premerge pipeline automatically fails if coverage drops below 95%

#### Logging Module Testing

The `tests/test_logging.py` file provides comprehensive testing coverage for the logging system with **38 test cases**:

- **Constants and Levels**: Verification of all logging constants and custom FATAL level (90) registration
- **Core Functionality**: Testing of main `log()` function and all convenience functions (`debug`, `info`, `warning`, `error`, `critical`, `fatal`, `exception`)
- **Configuration Functions**: Complete testing of `set_level()`, `configure_file_logging()`, and `get_logger()`
- **File Handler Management**: Tests for `add_file_handler()`, `remove_file_handlers()` with parent directory creation
- **Console Handler Control**: Testing of `set_console_output()`, `add_stdout_handler()`, `add_stderr_handler()`
- **Integration Tests**: Real logging output verification and file logging functionality
- **Error Handling**: Exception logging, fatal function with system exit, and validation error testing
- **Edge Cases**: Handler cleanup, custom formatting, propagation control, and initialization testing

#### Python Version Testing Matrix

The SDK officially supports Python 3.8+ but is tested on the following versions:
Expand Down Expand Up @@ -152,7 +173,17 @@ The `.github/workflows/premerge.yaml` workflow runs tests against all supported
### Key Implementation Details

- **Request/Response Wrappers**: The SDK provides `Request` and `Response` wrapper classes that encapsulate HTTP request/response data with additional functionality beyond raw httpx objects
- **Logging Integration**: File logging support via `add_file_handler()`, `remove_file_handlers()`, and `configure_file_logging()` functions with custom format strings and propagation control
- **Advanced Logging System**: Full-featured logging implementation with comprehensive functionality:
- **Custom Levels**: FATAL level (90) in addition to standard levels (DEBUG=10, INFO=20, WARNING=30, ERROR=40, CRITICAL=50)
- **Multiple Handlers**: File handlers, console handlers (stdout/stderr), with automatic cleanup
- **Configuration Functions**:
- `set_level(lvl, *, propagate=False)` - Set logging level with optional httpx/httpcore control
- `configure_file_logging(file_path, level=INFO, *, propagate=False, format_string=None)` - One-call setup
- `add_file_handler()`, `remove_file_handlers()` - File logging management
- `set_console_output()`, `add_stdout_handler()`, `add_stderr_handler()` - Console control
- **Convenience Functions**: `debug()`, `info()`, `warning()`, `error()`, `critical()`, `fatal()`, `exception()`
- **Automatic Features**: Directory creation, custom formatting, propagation control, handler cleanup
- **Google-style Documentation**: All functions have comprehensive docstrings with Args/Returns/Raises sections
- **Exception Handling**: Centralized exception classes in `exceptions.py` for consistent error handling across the SDK
- **JSON Utilities**: Dedicated `jsonutils.py` module for JSON serialization/deserialization operations
- **Metadata Management**: Version and package metadata handled via `metadata.py` with dynamic versioning from git using importlib.metadata
Expand Down
40 changes: 18 additions & 22 deletions docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,28 +66,28 @@ All tools are configured in `pyproject.toml` and can be run through `uv` or the
## Logging

By default all logging is turned off for `ipsdk`. To enable logging to
`stdout`, use the `ipsdk.logger.set_level` function.
`stdout`, use the `ipsdk.logging.set_level` function.

The SDK provides logging level constants that you can use instead of importing the standard library logging module:

```python
>>> import ipsdk

# Using ipsdk logging constants (recommended)
>>> ipsdk.logger.set_level(ipsdk.logger.DEBUG)
>>> ipsdk.logging.set_level(ipsdk.logging.DEBUG)
```

### Available Logging Levels

The SDK provides the following logging level constants:

- `ipsdk.logger.NOTSET` - No logging threshold (0)
- `ipsdk.logger.DEBUG` - Debug messages (10)
- `ipsdk.logger.INFO` - Informational messages (20)
- `ipsdk.logger.WARNING` - Warning messages (30)
- `ipsdk.logger.ERROR` - Error messages (40)
- `ipsdk.logger.CRITICAL` - Critical error messages (50)
- `ipsdk.logger.FATAL` - Fatal error messages (90)
- `ipsdk.logging.NOTSET` - No logging threshold (0)
- `ipsdk.logging.DEBUG` - Debug messages (10)
- `ipsdk.logging.INFO` - Informational messages (20)
- `ipsdk.logging.WARNING` - Warning messages (30)
- `ipsdk.logging.ERROR` - Error messages (40)
- `ipsdk.logging.CRITICAL` - Critical error messages (50)
- `ipsdk.logging.FATAL` - Fatal error messages (90)

### File Logging

Expand All @@ -101,11 +101,7 @@ The easiest way to enable both console and file logging:
>>> import ipsdk

# Enable both console and file logging
>>> ipsdk.logger.configure_file_logging("/path/to/app.log", level=ipsdk.logger.DEBUG)

# With propagation to httpx/httpcore loggers
>>> ipsdk.logger.configure_file_logging("/path/to/app.log", level=ipsdk.logger.INFO, propagate=True)
```
>>> ipsdk.logging.configure_file_logging("/path/to/app.log", level=ipsdk.logging.DEBUG)

#### Manual File Handler Management

Expand All @@ -115,17 +111,17 @@ For more control, you can add and remove file handlers manually:
>>> import ipsdk

# First set the console logging level
>>> ipsdk.logger.set_level(ipsdk.logger.INFO)
>>> ipsdk.logging.set_level(ipsdk.logging.INFO)

# Add a file handler
>>> ipsdk.logger.add_file_handler("/path/to/app.log")
>>> ipsdk.logging.add_file_handler("/path/to/app.log")

# Add multiple file handlers with different levels
>>> ipsdk.logger.add_file_handler("/path/to/debug.log", level=ipsdk.logger.DEBUG)
>>> ipsdk.logger.add_file_handler("/path/to/errors.log", level=ipsdk.logger.ERROR)
>>> ipsdk.logging.add_file_handler("/path/to/debug.log", level=ipsdk.logging.DEBUG)
>>> ipsdk.logging.add_file_handler("/path/to/errors.log", level=ipsdk.logging.ERROR)

# Remove all file handlers when done
>>> ipsdk.logger.remove_file_handlers()
>>> ipsdk.logging.remove_file_handlers()
```

#### Custom Log Formatting
Expand All @@ -134,10 +130,10 @@ You can specify custom format strings for file handlers:

```python
>>> custom_format = "%(asctime)s [%(levelname)s] %(message)s"
>>> ipsdk.logger.add_file_handler("/path/to/app.log", format_string=custom_format)
>>> ipsdk.logging.add_file_handler("/path/to/app.log", format_string=custom_format)

# Or with configure_file_logging
>>> ipsdk.logger.configure_file_logging("/path/to/app.log", format_string=custom_format)
>>> ipsdk.logging.configure_file_logging("/path/to/app.log", format_string=custom_format)
```

**Note:** File logging automatically creates parent directories if they don't exist.
**Note:** File logging automatically creates parent directories if they don't exist.
14 changes: 8 additions & 6 deletions docs/exceptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,38 +182,40 @@ Classifies an httpx exception into the appropriate SDK exception.
### Specific Exception Handling

```python
import ipsdk
from ipsdk.exceptions import NetworkError, AuthenticationError, ClientError

try:
# SDK operations
response = client.get("/api/resource")
except NetworkError as e:
# Handle network issues - maybe retry
logger.error(f"Network error: {e}")
ipsdk.logging.error(f"Network error: {e}")
except AuthenticationError as e:
# Handle auth issues - maybe refresh token
logger.error(f"Authentication failed: {e}")
ipsdk.logging.error(f"Authentication failed: {e}")
except ClientError as e:
# Handle client errors - maybe fix request
if e.status_code == 404:
logger.error("Resource not found")
ipsdk.logging.error("Resource not found")
else:
logger.error(f"Client error: {e}")
ipsdk.logging.error(f"Client error: {e}")
```

### General Exception Handling

```python
import ipsdk
from ipsdk.exceptions import IpsdkError

try:
# SDK operations
response = client.get("/api/resource")
except IpsdkError as e:
# Catch all SDK errors
logger.error(f"SDK error: {e.message}")
ipsdk.logging.error(f"SDK error: {e.message}")
if e.details:
logger.debug(f"Error details: {e.details}")
ipsdk.logging.debug(f"Error details: {e.details}")
```

### Exception Information Access
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
"S110", # try-except-pass (intentional for error handling)
]

"src/ipsdk/logger.py" = [
"src/ipsdk/logging.py" = [
"FBT001", # Boolean-typed positional argument (part of public API)
"FBT002", # Boolean default positional argument (part of public API)
"G004", # Logging statement uses f-string (acceptable for informational logging)
Expand Down
7 changes: 5 additions & 2 deletions src/ipsdk/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
# Copyright (c) 2025 Itential, Inc
# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)

from . import logger
from . import logging
from . import metadata
from .gateway import gateway_factory
from .platform import platform_factory

__version__ = metadata.version

__all__ = ("gateway_factory", "logger", "platform_factory")
__all__ = ("gateway_factory", "logging", "platform_factory")


logging.initialize()
Loading
Loading