Repository URL to install this package:
|
Version:
3.2.1 ▾
|
| sincpro_framework |
| LICENSE.md |
| README.md |
| pyproject.toml |
| PKG-INFO |
Here's a quick example to get you started with the Sincpro Framework:
from sincpro_framework import UseFramework, Feature, DataTransferObject # 1. Initialize the framework framework = UseFramework("cybersource") # 2. Add Dependencies (Example dependencies) from sincpro_framework import Database db = Database() framework.add_dependency("db", db) # 3. Error Handler (Optional) framework.add_global_error_handler(lambda e: print(f"Error: {e}")) # 4. Create a Use Case with DTOs class GreetingParams(DataTransferObject): name: str @framework.feature(GreetingParams) class GreetingFeature(Feature): def execute(self, dto: GreetingParams) -> str: self.db.store(f"Greeting {dto.name}") return f"Hello, {dto.name}!" # 5. Execute the Use Case if __name__ == "__main__": # Create an instance of the parameter DTO greeting_dto = GreetingParams(name="Alice") # Execute the feature result = framework(greeting_dto) print(result) # Output: Hello, Alice!
Now you are ready to explore more complex use cases! 🚀
Hexagonal Architecture, also known as Ports and Adapters, is an architectural approach that aims to decouple core business logic from external dependencies. It organizes the system into distinct layers: domain, application, and infrastructure, enhancing maintainability, scalability, and adaptability.
The Sincpro Framework adopts a unified bus pattern as a single point of entry for managing use cases, dependencies, and services within a bounded context. This simplifies the architecture by encapsulating all requirements of a given context, ensuring a clear and consistent structure.
Using a unified bus allows developers to access all dependencies through a single environment, eliminating the need for repeated imports or initialization. This approach ensures each bounded context is self-sufficient, independently scalable, and minimizes coupling while enhancing modularity.
The Sincpro Framework follows hexagonal architecture principles, promoting modularity, scalability, and development efficiency. Here are its core features:
contextvars for thread-safe context storage and isolation.# Simple context usage with app.context({"correlation_id": "123", "user.id": "admin"}) as app_with_context: result = app_with_context(some_dto) # Context automatically available in handlers # Nested contexts with overrides with app.context({"env": "prod", "user": "admin"}) as outer_app: with outer_app.context({"env": "staging"}) as inner_app: # Override env, inherit user inner_app(dto) # env="staging", user="admin" # Access context in Features and ApplicationServices class PaymentFeature(Feature): def execute(self, dto: PaymentDTO) -> PaymentResponse: correlation_id = self.context.get("correlation_id") user_id = self.context.get("user.id") # Use context in business logic...
The following example shows how to configure the Sincpro Framework for a payment gateway integration, such as CyberSource. It is recommended to name the framework instance to clearly represent the bounded context it serves.
To set up the Sincpro Framework, configuration should be performed at the application layer within the use_cases
directory of each bounded context.
sincpro_payments_sdk/ ├── pyproject.toml ├── README.md ├── apps/ │ ├── cybersource/ │ │ ├── adapters/ │ │ │ ├── cybersource_rest_api_adapter.py │ │ │ └── __init__.py │ │ ├── domain/ │ │ │ ├── card.py │ │ │ ├── customer.py │ │ │ └── __init__.py │ │ ├── infrastructure/ │ │ │ ├── logger.py │ │ │ ├── aws_services.py │ │ │ ├── orm.py │ │ │ └── __init__.py │ │ └── use_cases/ │ │ ├── tokenization/ │ │ │ ├── new_tokenization_feature.py │ │ │ └── __init__.py │ │ ├── payments/ │ │ │ ├── token_and_payment_service.py │ │ │ └── __init__.py │ │ └── __init__.py │ ├── qr/ │ ├── sms_payment/ │ ├── bank_api/ │ ├── online_payment_gateway/ │ └── paypal_integration/ └── tests
Each use case should import both the DTO for input parameters and the DTO for responses to maintain clarity and consistency.
__init__.pyfrom typing import Type from sincpro_framework import Feature as _Feature from sincpro_framework import UseFramework as _UseFramework from sincpro_framework import ApplicationService as _ApplicationService from sincpro_payments_sdk.apps.cybersource.adapters.cybersource_rest_api_adapter import ( ESupportedCardType, TokenizationAdapter, ) from sincpro_payments_sdk.infrastructure.orm import with_transaction as db_session from sincpro_payments_sdk.infrastructure.aws_services import AwsService as aws_service # Create an instance of the framework cybersource = _UseFramework() # Register dependencies cybersource.add_dependency("token_adapter", TokenizationAdapter()) cybersource.add_dependency("ECardType", ESupportedCardType) cybersource.add_dependency("db_session", db_session) cybersource.add_dependency("aws_service", aws_service) # Define a custom Feature class to access the dependencies class Feature(_Feature): token_adapter: TokenizationAdapter ECardType: Type[ESupportedCardType] db_session: ... aws_service: ... logger: ... # Define a custom Application Service class to access dependencies class ApplicationService(_ApplicationService): token_adapter: TokenizationAdapter ECardType: Type[ESupportedCardType] db_session: ... aws_service: ... logger: ... feature_bus: ... # Add use cases (Application Services and Features) from . import tokenization __all__ = ["cybersource", "tokenization", "Feature"]
When bootstrapping a bounded context with UseFramework, the recommended practice is to split
framework wiring into three dedicated files under apps/<domain>/infrastructure/:
apps/ └── my_domain/ ├── infrastructure/ │ ├── dependencies.py # registers adapters; declares DependencyContextType │ ├── framework.py # defines local Feature/ApplicationService + config_framework() │ └── error_handler.py # (optional) registers error handlers ├── services/ │ ├── feature_a.py │ └── feature_b.py └── __init__.py # creates the framework instance and imports services
dependencies.py — Adapter RegistrationDeclare all external adapters in one place and expose a DependencyContextType typing helper.
This class is not instantiated — it is used only as a mixin to give Feature and
ApplicationService subclasses IDE autocomplete for injected attributes.
# apps/my_domain/infrastructure/dependencies.py from sincpro_framework import UseFramework from my_sdk.adapters import PaymentAdapter, TokenizationAdapter class DependencyContextType: """Typing helper — gives Features/AppServices IDE autocomplete for injected deps.""" token_adapter: TokenizationAdapter payment_adapter: PaymentAdapter def register_dependencies(framework: UseFramework) -> UseFramework: """Register all adapters with the framework instance.""" framework.add_dependency("token_adapter", TokenizationAdapter()) framework.add_dependency("payment_adapter", PaymentAdapter()) return framework
framework.py — Wiring with DependencyContextTypeCombine the framework base classes with DependencyContextType using multiple inheritance so that
every Feature and ApplicationService in this bounded context automatically inherits the typed
attributes.
# apps/my_domain/infrastructure/framework.py from sincpro_framework import ApplicationService as _ApplicationService from sincpro_framework import DataTransferObject # re-exported for convenience from sincpro_framework import Feature as _Feature from sincpro_framework import UseFramework from .dependencies import DependencyContextType, register_dependencies class Feature(_Feature, DependencyContextType): """Base Feature for this bounded context — typed deps included.""" pass class ApplicationService(_ApplicationService, DependencyContextType): """Base ApplicationService for this bounded context — typed deps included.""" pass def config_framework(name: str) -> UseFramework: """Create and configure the framework instance.""" instance = UseFramework(name) register_dependencies(instance) return instance
__init__.py — Bootstrap the Bounded ContextCreate the framework instance first, then import the service modules so that the @framework.feature
and @framework.app_service decorators register against the already-created instance.
# apps/my_domain/__init__.py from .infrastructure.framework import ( ApplicationService, DataTransferObject, Feature, config_framework, ) my_framework = config_framework("my-domain") # Import services AFTER creating the instance so decorators register against it from .services import feature_a, feature_b # noqa: E402, F401
Register a dedicated test feature to verify that every attribute declared in DependencyContextType
is actually injected at runtime. If a new dependency is added to DependencyContextType but
forgotten in register_dependencies, this test catches it automatically.
# tests/my_domain/test_framework_setup.py from my_sdk.apps.my_domain import DataTransferObject, Feature, my_framework from my_sdk.apps.my_domain.infrastructure.dependencies import DependencyContextType class CommandVerifyDeps(DataTransferObject): pass class ResponseVerifyDeps(DataTransferObject): ok: bool # Register at module level — not inside the test function — so the decorator runs # before any other test in the session builds the framework bus. @my_framework.feature(CommandVerifyDeps) class VerifyDepsFeature(Feature): def execute(self, dto: CommandVerifyDeps) -> ResponseVerifyDeps: for dep_name in DependencyContextType.__annotations__: assert getattr(self, dep_name, None) is not None, f"Missing dep: {dep_name}" return ResponseVerifyDeps(ok=True) def test_framework_dependencies_injected_in_feature(): """All deps declared in DependencyContextType must be accessible inside a Feature.""" result = my_framework(CommandVerifyDeps(), ResponseVerifyDeps) assert result.ok is True
Why this matters:
DependencyContextType.__annotations__ automatically — adding a new dependency to
the context covers it in the test without any manual edits.DependencyContextType and what is actually
registered via add_dependency.To create a new Feature, follow these steps:
use_cases.DataTransferObject.DataTransferObject to create classes for input parameters and
responses.Feature class by inheriting from the custom Feature class.from sincpro_payments_sdk.apps.cybersource import cybersource, DataTransferObject, Feature # Define parameter DTO class TokenizationParams(DataTransferObject): card_number: str expiration_date: str cardholder_name: str # Define response DTO class TokenizationResponse(DataTransferObject): token: str status: str # Create the Feature class @cybersource.feature(TokenizationParams) class NewTokenizationFeature(Feature): def execute(self, dto: TokenizationParams) -> TokenizationResponse: # Example usage of dependencies cybersource.logger.info("Starting tokenization process") token = self.token_adapter.create_token( card_number=dto.card_number, expiration_date=dto.expiration_date, cardholder_name=dto.cardholder_name ) return TokenizationResponse(token=token, status="success")
ApplicationService is used to coordinate multiple features while maintaining reusability and consistency. It orchestrates features into cohesive workflows.
from sincpro_payments_sdk.apps.cybersource import cybersource, DataTransferObject, ApplicationService from sincpro_payments_sdk.apps.cybersource.use_cases.tokenization import TokenizationParams # Define parameter DTO class PaymentServiceParams(DataTransferObject): card_number: str expiration_date: str cardholder_name: str amount: float # Define response DTO class PaymentServiceResponse(DataTransferObject): status: str transaction_id: str # Create the Application Service class @cybersource.app_service(PaymentServiceParams) class PaymentOrchestrationService(ApplicationService): def execute(self, dto: PaymentServiceParams) -> PaymentServiceResponse: # Create the command DTO for tokenization tokenization_command = TokenizationParams( card_number=dto.card_number, expiration_date=dto.expiration_date, cardholder_name=dto.cardholder_name ) tokenization_result = self.feature_bus.execute(tokenization_command) # Example usage of dependencies cybersource.logger.info("Proceeding with payment after tokenization") # Proceed with payment using the token (pseudo code for payment processing) transaction_id = "12345" # Simulated transaction ID return PaymentServiceResponse(status="success", transaction_id=transaction_id)
Once a Feature or ApplicationService is defined, it can be executed by passing the appropriate DTO instance.
from sincpro_payments_sdk.apps.cybersource import cybersource from sincpro_payments_sdk.apps.cybersource.use_cases.tokenization import TokenizationParams, TokenizationResponse from sincpro_payments_sdk.apps.cybersource.use_cases.payments import PaymentServiceParams, PaymentServiceResponse # Example of executing a Feature feature_dto = TokenizationParams( card_number="4111111111111111", expiration_date="12/25", cardholder_name="John Doe" ) # Execute the feature feature_result = cybersource(feature_dto, TokenizationResponse) print(f"Tokenization Result: {feature_result.token}, Status: {feature_result.status}") # Example of executing an Application Service service_dto = PaymentServiceParams( card_number="4111111111111111", expiration_date="12/25", cardholder_name="John Doe", amount=100.00 ) # Execute the application service service_result = cybersource(service_dto, PaymentServiceResponse) print(f"Payment Status: {service_result.status}, Transaction ID: {service_result.transaction_id}")
The Sincpro Framework provides a robust solution for managing the application layer within a hexagonal architecture. By focusing on decoupling business logic from external dependencies, the framework promotes modularity, scalability, and maintainability.
This structured approach ensures high-quality, maintainable software that can adapt to evolving business needs. 🚀
The Sincpro Framework provides a simple and flexible middleware system that lets you add custom processing logic before your Features and ApplicationServices are executed.
The middleware system follows the framework's core principles:
Middleware are plain functions that:
from typing import Any def my_middleware(dto: Any) -> Any: """Simple middleware that validates or transforms a DTO.""" if hasattr(dto, 'amount') and dto.amount <= 0: raise ValueError("Amount must be positive") return dto
from sincpro_framework import UseFramework def validate_payment(dto): if hasattr(dto, 'amount') and dto.amount <= 0: raise ValueError("Amount must be positive") return dto def add_timestamp(dto): import time if hasattr(dto, '__dict__'): dto.timestamp = time.time() return dto framework = UseFramework("my_app") framework.add_middleware(validate_payment) framework.add_middleware(add_timestamp) # All DTOs are processed by middleware before reaching the Feature/Service result = framework(my_dto)
Middleware execute in the order they are added:
def validate_user_input(dto): if hasattr(dto, 'email') and '@' not in dto.email: raise ValueError("Invalid email format") return dto
def check_authentication(dto): if hasattr(dto, 'user_id') and not is_authenticated(dto.user_id): raise PermissionError("User not authenticated") return dto
def enrich_user_data(dto): if hasattr(dto, 'user_id'): dto.user_profile = get_user_profile(dto.user_id) return dto
import logging def log_requests(dto): logging.info(f"Processing DTO: {type(dto).__name__}") return dto
If any middleware raises an exception, the entire pipeline stops and the exception propagates to the caller:
def strict_validation(dto): if not hasattr(dto, 'required_field'): raise ValueError("required_field is missing") return dto framework.add_middleware(strict_validation) result = framework(my_dto) # Raises ValueError if required_field is missing
The framework provides three independent error handler scopes: global (framework bus), feature, and app service. Register a handler with the corresponding method — handlers can be added before or after the first execution and always take effect immediately.
An error handler receives the exception. Return a value to suppress it:
from sincpro_framework import UseFramework framework = UseFramework("my_app") def handle_error(error: Exception): return {"error": str(error)} # suppresses the exception framework.add_global_error_handler(handle_error)
Each scope intercepts only the errors produced at that level:
# Only feature errors framework.add_feature_error_handler(feature_handler) # Only app service errors framework.add_app_service_error_handler(app_service_handler) # Everything that reaches the root bus framework.add_global_error_handler(global_handler)
Handlers can be registered at any point — before the bus is built or after — and take effect immediately:
framework = UseFramework("my_app") framework.add_global_error_handler(base_handler) # before first execution framework(some_dto) # first call triggers build framework.add_global_error_handler(extra_handler) # after build — also works
Every call to add_*_error_handler adds the handler to a chain. The first registered handler executes first. If it re-raises, the framework automatically delegates to the next handler in the chain.
| Registration order | Role | Executes |
|---|---|---|
add(h1) first |
Auth — intercepts auth errors early | First |
add(h2) second |
Logging — records the error, then delegates | Second |
add(h3) third |
Base — produces the structured error response | Last |
# Registration order: auth → observability → base # Execution order: auth → observability → base def auth_handler(error: Exception): """First — intercepts auth errors; delegates everything else.""" if isinstance(error, AuthenticationError): return {"ok": False, "detail": "unauthenticated", "code": 401} raise error # delegates to observability_handler def observability_handler(error: Exception): """Second — logs error, then delegates to base_handler.""" log.error("unhandled error", exc_info=error) raise error # delegates to base_handler def base_handler(error: Exception): """Last — always returns a structured error, never raises.""" return {"ok": False, "detail": str(error)} framework.add_global_error_handler(auth_handler) # 1st = runs first framework.add_global_error_handler(observability_handler) # 2nd framework.add_global_error_handler(base_handler) # 3rd = final fallback
The Sincpro Framework includes a powerful auto-documentation feature that automatically generates comprehensive documentation for your framework instances. This documentation includes all your DTOs, Features, Application Services, Dependencies, and Middlewares in multiple formats optimized for different use cases.
The easiest way to generate documentation for your project:
from sincpro_framework.generate_documentation import build_documentation # Import your framework instances from their respective modules from apps.payment_gateway import payment_framework from apps.user_management import user_framework # Generate traditional markdown documentation (default) build_documentation( [payment_framework, user_framework], output_dir="docs/generated" ) # Generate AI-optimized JSON schema build_documentation( [payment_framework, user_framework], output_dir="docs/generated", format="json" ) # Generate chunked JSON for optimal AI consumption (NEW!) build_documentation( [payment_framework, user_framework], output_dir="docs/generated", format="json", chunked=True ) # Generate both formats build_documentation( [payment_framework, user_framework], output_dir="docs/generated", format="both" )
docs/generated/
├── mkdocs.yml # MkDocs configuration
├── requirements.txt # Dependencies
├── framework_schema.json # AI-optimized JSON with framework context
├── site/ # Built HTML documentation
└── docs/ # Markdown content
├── index.md # Overview
├── features.md # Features documentation
├── dtos.md # DTOs documentation
└── application-services.md # Services documentation
docs/generated/ai_context/
├── 01_framework_context.json # Shared framework knowledge (70KB)
├── 01_payment_gateway_context.json # Instance overview (1-2KB)
├── 01_payment_gateway_dtos.json # DTO summaries (700B)
├── 01_payment_gateway_dtos_details.json # Full DTO details (1-3KB)
├── 01_payment_gateway_features.json # Feature summaries (700B)
├── 01_payment_gateway_features_details.json # Full feature details (1-3KB)
├── 01_payment_gateway_services.json # Service summaries (if any)
├── 01_payment_gateway_services_details.json # Full service details
├── 02_user_management_context.json # Second instance overview
├── 02_user_management_dtos.json # Second instance DTOs
└── ... # Additional instances
The enhanced JSON schema combines framework context with repository analysis for complete AI understanding:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Repository Schema with Framework Context", "schema_type": "ai_optimized_complete", "framework_context": { "framework_name": "Sincpro Framework", "core_principles": {/* Framework usage patterns and principles */}, "key_features": {/* Framework capabilities and features */}, "framework_execution_patterns": {/* How to execute features/services */} }, "repository_analysis": { "metadata": { "architecture_patterns": ["DDD", "Clean Architecture"], "component_summary": { /* counts and statistics */ } }, "components": { "dtos": [/* with AI hints for type classification */], "features": [/* with business domain inference */], "application_services": [/* with orchestration patterns */] } }, "ai_integration": { "framework_integration": { "execution_patterns": {/* How to use framework with repository components */}, "available_features": {/* Framework capabilities */} }, "complete_understanding": { "framework_knowledge": "Loaded from hardcoded guide", "repository_knowledge": "Generated from code analysis", "ai_capability": "Complete understanding of framework usage + repository components" }, "usage_synthesis": { "how_to_execute_features": {/* Real examples combining framework + repo */}, "how_to_execute_services": {/* Real examples combining framework + repo */} }, "embedding_suggestions": { "primary_entities": ["PaymentCommand", "UserCommand"], "business_capabilities": ["PaymentFeature", "UserFeature"] }, "code_generation_hints": { "framework_patterns": ["command_pattern", "dependency_injection"], "common_imports": ["from sincpro_framework import..."] }, "complexity_analysis": { "overall_complexity": "medium", "most_complex_components": ["ComplexService"] } } }
The new chunked approach provides significant advantages for AI systems:
Start with Framework Context (01_framework_context.json - 70KB)
Instance Overview (01_<name>_context.json - 1-2KB each)
Component Summaries (01_<name>_dtos.json - 700B each)
Detailed Information (01_<name>_dtos_details.json - 1-3KB each)
Example of well-documented code:
class PaymentCommand(DataTransferObject): """Command for processing credit card payments. This DTO contains all necessary information to process a payment transaction through the payment gateway. """ card_number: str # Credit card number (PCI compliant) amount: float # Payment amount in USD merchant_id: str # Unique merchant identifier @framework.feature(PaymentCommand) class PaymentFeature(Feature): """Process payment transactions through external gateway. This feature handles the complete payment flow including validation, gateway communication, and response processing. """ def execute(self, dto: PaymentCommand) -> PaymentResponse: """Execute payment processing. Args: dto: Payment command with card and amount information Returns: PaymentResponse: Transaction result with ID and status Raises: PaymentError: When payment fails or card is invalid """ # Implementation here...
The JSON schema format enables powerful AI integrations:
The framework provides built-in log correlation out of the box and optional distributed tracing via OpenTelemetry.
Every framework(dto) call automatically tags all internal log lines with a trace_id and span_id. These are UUID-based identifiers — enough to correlate all logs produced by a single execution even without an external tracing backend.
You can also read them inside any Feature or ApplicationService:
class MyFeature(Feature): def execute(self, dto: MyDTO) -> MyResponse: trace_id = self.context.get("trace_id") # always present ...
pip install sincpro-framework[opentelemetry]
This installs:
opentelemetry-api + opentelemetry-sdkopentelemetry-exporter-otlp-proto-grpc (primary)opentelemetry-exporter-otlp-proto-http (fallback)| Without OTel | With OTel |
|---|---|
| UUID-based trace/span IDs in logs | Real OTel spans with proper trace IDs |
| No span hierarchy | ApplicationService span wraps Feature spans |
| No OTLP export | Exports to Jaeger, Grafana Tempo, Honeycomb, etc. |
| No W3C traceparent propagation | Parent context from HTTP headers via carrier= |
| No external span adoption | Auto-adopts active span from FastAPI, Celery, etc. |
When OTel is installed, auto-adoption of outer spans happens transparently — any active span already in the OTel context (set by FastAPI OpenTelemetry middleware, a Celery task decorator, etc.) becomes the parent of all sincpro spans without any extra setup.
Set the endpoint in your environment or the framework config file:
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
Or in sincpro_framework/conf/sincpro_framework_conf.yml:
otlp_endpoint: $ENV:OTEL_EXPORTER_OTLP_ENDPOINT
Register the provider once at startup, before any framework(dto) call:
from sincpro_framework.tracing import setup_otlp_provider setup_otlp_provider("payments-service")
The provider is a process-level singleton — only the first call registers it. Subsequent calls (e.g. from a second bounded context) are no-ops.
with_trace() context managerUse with_trace() when you need explicit control over the trace boundary — for example, to group multiple framework(dto) calls under a single trace, or to accept a trace from an upstream caller.
Fresh trace (useful in CLI scripts, workers, or test harnesses):
with framework.with_trace() as fw: result = fw(MyDTO(...), MyResponse) # all logs inside this block share the same trace_id/span_id
Propagate from upstream HTTP headers (W3C traceparent):
# headers = {"traceparent": "00-<trace_id>-<parent_id>-01", ...} with framework.with_trace(carrier=request.headers) as fw: result = fw(ProcessOrderDTO(...), OrderResult)
Explicit IDs (re-use IDs from a previous system that doesn't speak W3C):
with framework.with_trace(trace_id="abc123", span_id="def456") as fw: result = fw(MyDTO(...), MyResponse)
Every span produced by the framework carries:
| Attribute | Value | Purpose |
|---|---|---|
sincpro.layer |
"feature" or "application_service" |
Identify which bus layer handled the DTO |
sincpro.instance |
The framework instance name (e.g. "payments") |
Distinguish bounded contexts — useful when multiple UseFramework instances share one process, since the OTLP service.name Resource reflects only the first registered instance |
setup_otlp_provider always creates a private TracerProvider for sincpro with its own service.name. If the host application (Odoo, FastAPI, Celery) already registered the global OTel provider, sincpro leaves it untouched and keeps its provider internal.
The trace relationship is still preserved: OTel propagates the active parent span via contextvars (process-wide), so sincpro spans are automatically children of whatever span the host has active at call time. In Tempo/Jaeger the full tree is visible and filterable:
service.name=odoo → GET /web/dataset/call_kw (Odoo HTTP span)
service.name=my-service → └── CreateOrderDTO (application_service)
service.name=my-service → └── ValidateStockDTO (feature)
Sampling is respected across the boundary: sincpro uses ParentBased(root=ALWAYS_ON), so when the host did not sample a trace, sincpro's spans are also dropped. When running standalone (no host parent span), every span is sampled.
The framework comes with a module or component to allow us to create configuratio or settings based on files or
environment variables.
You need to inherit from SincproConfig from module sincpro_framework.sincpro_conf
from sincpro_framework.sincpro_conf import SincproConfig class PostgresConf(SincproConfig): host: str = "localhost" port: int = 5432 user: str = "my_user" class MyConfig(SincproConfig): log_level: str = "DEBUG" token: str = "defult_my_token" postgresql: PostgresConf = PostgresConf()
This class should be mapped based on yaml file like this, we have a feature to use ENV variables in the yaml file
using the prefix $ENV:
log_level: "INFO" token: "$ENV:MY_SECRET_TOKEN" postgresql: host: localhost port: 12345 user: custom_user
When using $ENV: prefix in your configuration files, the framework will:
$ENV:This behavior allows applications to run with partial configurations in development environments or when not all environment variables are available, while still logging appropriate warnings.
Example of fallback to default values:
# Configuration class with default class ApiConfig(SincproConfig): api_key: str = "dev_default_key" # Default value as fallback # In config.yml api_key: "$ENV:API_KEY" # References environment variable # If API_KEY environment variable is not set, the framework will: # 1. Log a warning: "Environment variable [API_KEY] is not set for field [api_key]. Using default value: dev_default_key" # 2. Use the default value "dev_default_key" # 3. Continue execution without error
Then you can use the config object in your code where it will be loaded all the settings from the yaml file
for that you will require use the following funciton build_config_obj
from sincpro_framework.sincpro_conf import build_config_obj from .my_config import MyConfig config = build_config_obj(MyConfig, '/path/to/your/config.yml') assert isinstance(config.log_level, str) assert isinstance(config.postgresql, PostgresConf)
The framework use a default setting file where live in the module folder inside of
sincpro_framework/conf/sincpro_framework_conf.yml
where you can define some behavior currently we support the following settings:
sincpro_framework_log_level: Log level for the framework logger. Default: DEBUG.otlp_endpoint: OTLP exporter endpoint for distributed tracing. Resolved from OTEL_EXPORTER_OTLP_ENDPOINT env var. Default: null (tracing disabled). Requires sincpro-framework[opentelemetry].Override the config file using another
export SINCPRO_FRAMEWORK_CONFIG_FILE = /path/to/your/config.yml