# Elytra PIM Client A fully Pythonic and **Pydantic-driven** client for the Elytra PIM (Product Information Management) API. ## Features - ๐Ÿ Fully Pythonic with Pydantic v2 data validation - ๐Ÿ“ฆ Auto-generated Pydantic models from OpenAPI specification - ๐Ÿ” Bearer token authentication - โœ… Request/Response validation with Pydantic - ๐ŸŒ Multi-language support - ๐Ÿ“„ Full type hints throughout the codebase - ๐Ÿงช Comprehensive error handling - ๐Ÿ”„ Context manager support - ๐Ÿ”„ Automatic serialization/deserialization ## Installation ### From Forgejo PyPI Install directly from the HIM-public package registry: ```bash pip install --index-url https://git.him-tools.de/api/packages/HIM-public/pypi/simple/ elytra-pim-client ``` ### From Source Clone the repository: ```bash git clone https://git.him-tools.de/HIM-public/elytra_client.git cd elytra_client pip install -e . ``` ### With Development Dependencies ```bash pip install -e ".[dev]" ``` ## Quick Start ### Basic Usage ```python from elytra_client import ElytraClient, SingleProductResponse # Initialize the client client = ElytraClient( base_url="https://example.com/api/v1", api_key="your-api-key" ) # Get all products - returns dict with Pydantic validated items products_response = client.get_products(lang="en", page=1, limit=10) for product in products_response["items"]: print(f"Product: {product.productName}, ID: {product.id}") # Get a specific product - returns Pydantic model directly product: SingleProductResponse = client.get_product(product_id=123, lang="en") print(f"Name: {product.productName}") print(f"Status: {product.objectStatus}") # Close the client client.close() ``` ### Creating Products with Validation ```python from elytra_client import ( ElytraClient, SingleNewProductRequestBody, AttributeRequestBody, ) with ElytraClient(base_url="https://example.com/api/v1", api_key="your-api-key") as client: # Create a new product with validated Pydantic model new_product = SingleNewProductRequestBody( productName="NEW-PRODUCT-001", parentId=1, attributeGroupId=10, attributes=[ AttributeRequestBody( attributeId=1, value="Sample Value", languageCode="en" ) ] ) # Validation happens automatically created_product = client.create_product(new_product) print(f"Created product ID: {created_product.id}") ``` ### Environment Variable Configuration Set your environment variables: ```bash export ELYTRA_BASE_URL="https://example.com/api/v1" export ELYTRA_API_KEY="your-api-key" export ELYTRA_TIMEOUT="30" export ELYTRA_VERIFY_SSL="true" ``` Then load from environment: ```python from elytra_client import ElytraClient from elytra_client.config import ElytraConfig config = ElytraConfig.from_env() client = ElytraClient(base_url=config.base_url, api_key=config.api_key) ``` ## Legacy REST API (Lobster PIM) The package also includes a subpackage for the older Lobster PIM REST API, which provides access to scheduled jobs and protocol logs that are not yet available in the newer API. ### Quick Start ```python from elytra_client.rest_api import LobsterRestApiClient, RestApiAuth # Create authentication auth = RestApiAuth.from_username_password("username", "password") # Create client for the REST API client = LobsterRestApiClient("http://lobster-server:8080", auth=auth) # Get all active jobs jobs = client.get_all_active_jobs() for job in jobs.jobInfoObjects: print(f"Job: {job.name} - Status: {job.status}") # Execute a job result = client.execute_job(job_id=123) print(f"Execution started with runtime ID: {result.runtimeId}") # Get protocol/logs protocols = client.get_protocols() ``` ### Features - ๐Ÿ“‹ **Job Management**: Access, monitor, and execute scheduled jobs - ๐Ÿ“œ **Protocol/Logs**: Retrieve execution logs and protocol information - ๐Ÿ” **Flexible Authentication**: Username/password or API token authentication - โณ **Job Control**: Execute jobs with parameter overrides and queue management - ๐ŸŽฏ **Type Safety**: Full Pydantic validation for all responses ### Authentication Methods ```python # Username/Password auth = RestApiAuth.from_username_password("admin", "password") # API Token (domain-specific) auth = RestApiAuth.from_api_token("admin", "token-id", domain="Jobs") ``` ### Documentation See [elytra_client/rest_api/README.md](elytra_client/rest_api/README.md) for comprehensive REST API documentation, including: - Complete API reference - Authentication details - Job management examples - Protocol/log access - Error handling - Common usage scenarios ## Webhooks (Elytra Event API) The package includes a subpackage for handling CloudEvents-based webhooks from the Elytra PIM Event API. ### Quick Start ```python from elytra_client.webhooks import CloudEvent, parse_webhook_payload # Parse an incoming webhook payload event = parse_webhook_payload(request.json()) print(f"Event type: {event.eventtype}") print(f"Object ID: {event.get_object_id()}") ``` ### With Authentication Validation ```python from elytra_client.webhooks import WebhookValidator, BasicAuth auth = BasicAuth(username="user", password="pass") validator = WebhookValidator(auth_type="basic", auth=auth) event = validator.validate_and_parse( payload=request.json(), headers=dict(request.headers) ) ``` ### Features - ๐Ÿ“จ **CloudEvents Support**: Strongly-typed Pydantic models following the CNCF CloudEvents v1.0 spec - ๐Ÿ” **Flexible Authentication**: Basic, Bearer token, and API Key authentication - ๐Ÿ”€ **Event Routing**: `SimpleWebhookEventDispatcher` to route events by type and operation to dedicated handlers - ๐Ÿ” **Retry Logic**: Exponential backoff retry policies for reliable webhook delivery - ๐ŸŒ **Framework Integration**: Ready-to-use examples for Flask and FastAPI ### Authentication Methods ```python # Basic authentication auth = BasicAuth(username="admin", password="secret") # Bearer token auth = BearerAuth(token="eyJhbGciOiJIUzI1NiIs...") # API Key (custom header) auth = APIKeyAuth(api_key="sk_live_secret123", header_name="X-API-Key") ``` ### Event Types and Operations Events carry an `EventType` (e.g., `PRODUCT`, `PRODUCT_GROUP`, `PRODUCT_ATTRIBUTE_VALUE`) and an `Operation` (`ADD`, `MODIFY`, `REMOVE`, `LINK`, `UNLINK`). ### Documentation See [elytra_client/webhooks/README.md](elytra_client/webhooks/README.md) for comprehensive webhook documentation, including: - Complete API reference - Authentication details - Event routing and dispatching - Retry logic configuration - Flask and FastAPI integration examples ## API Methods All methods return Pydantic models with full type validation and IDE autocompletion support. ### Products - `get_products(...) -> Dict` - Get all products (items are `SingleProductResponse` Pydantic models) - `get_product(id, lang) -> SingleProductResponse` - Get single product - `create_product(data) -> SingleProductResponse` - Create new product with validation - `update_product(data) -> SingleProductResponse` - Update product with validation - `delete_product(id) -> Dict` - Delete product ### Product Groups - `get_product_groups(...) -> Dict` - Get all product groups (items are `SingleProductGroupResponse` models) - `get_product_group(id, lang) -> SingleProductGroupResponse` - Get single product group - `create_product_group(data) -> SingleProductGroupResponse` - Create new product group - `update_product_group(data) -> SingleProductGroupResponse` - Update product group - `delete_product_group(id) -> Dict` - Delete product group ### Attributes - `get_attributes(...) -> Dict` - Get all attributes (items are `SingleAttributeResponse` models) - `get_attribute(id, lang) -> SingleAttributeResponse` - Get single attribute ### Health Check - `health_check() -> Dict` - Check API health status ## Error Handling The client provides specific exception classes for different error types: ```python from elytra_client import ElytraClient from elytra_client.exceptions import ( ElytraAuthenticationError, ElytraNotFoundError, ElytraValidationError, ElytraAPIError, ) from pydantic import ValidationError try: client = ElytraClient(base_url="https://example.com/api/v1", api_key="invalid-key") product = client.get_product(123) except ElytraAuthenticationError: print("Authentication failed") except ElytraNotFoundError: print("Product not found") except ElytraValidationError as e: print(f"API response validation failed: {e}") except ValidationError as e: print(f"Request model validation failed: {e}") except ElytraAPIError as e: print(f"API error: {e}") ``` ### Validation - **Request validation**: Pydantic models validate all input before sending to API - **Response validation**: Pydantic models validate API responses for data integrity - **Automatic deserialization**: Responses are automatically converted to Pydantic models ## Pydantic Models All request and response models are automatically generated from the OpenAPI specification using [datamodel-code-generator](https://github.com/koxudaxi/datamodel-code-generator). ### Available Models - **Response Models**: `SingleProductResponse`, `SingleProductGroupResponse`, `SingleAttributeResponse`, etc. - **Request Models**: `SingleNewProductRequestBody`, `SingleUpdateProductRequestBody`, `SingleNewProductGroupRequestBody`, etc. - **Attribute Models**: `ProductAttributeResponse`, `AttributeRequestBody` All models include: - โœ… Full type hints and validation - โœ… Documentation from OpenAPI spec - โœ… IDE autocompletion support - โœ… Automatic serialization/deserialization ### Regenerating Models To regenerate models from the OpenAPI spec: ```bash python -m datamodel_code_generator --input openapi.yaml --input-file-type openapi --output elytra_client/models.py --target-python-version 3.10 ``` ## Development ### Running Tests ```bash pytest ``` ### Code Quality Format code with Black: ```bash black elytra_client tests ``` Check with flake8: ```bash flake8 elytra_client tests ``` Type checking with mypy: ```bash mypy elytra_client ``` ## API Documentation For complete API documentation, refer to the OpenAPI specification in `openapi.yaml` see here: https://w4services.atlassian.net/wiki/spaces/pimdocext/pages/426803205/Web+API or visit the Elytra website: https://www.elytra.ch/ ## Contact For support on this client, please email: info@hoerl-im.de ## License MIT License - see LICENSE file for details