timber-common 1.2.0__tar.gz → 1.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {timber_common-1.2.0 → timber_common-1.2.2}/CHANGELOG.md +29 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/PKG-INFO +1 -1
- {timber_common-1.2.0 → timber_common-1.2.2}/common/models/base.py +2 -1
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/db_service.py +18 -23
- timber_common-1.2.2/common/utils/db_url.py +41 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/utils/db_utils.py +2 -1
- {timber_common-1.2.0 → timber_common-1.2.2}/pyproject.toml +1 -1
- {timber_common-1.2.0 → timber_common-1.2.2}/LICENSE +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/README.md +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/__init__.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/cli/__init__.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/cli/commands/__init__.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/cli/commands/validate.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/cli/main.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/config/__init__.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/config/model_loader.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/engine/__init__.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/engine/config_executor.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/engine/operation_registry.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/init.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/models/__init__.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/models/a2ui.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/models/configs/__init__.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/models/core/__init.__.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/models/core/enterprise.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/models/core/tag.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/models/core/user.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/models/factory.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/models/mixins.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/models/registry.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/plugins/__init__.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/plugins/contract.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/plugins/discovery.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/render/__init__.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/render/pdf.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/schemas/README.md +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/schemas/__init__.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/schemas/base.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/schemas/workflow/__init__.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/schemas/workflow/definition.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/schemas/workflow/enums.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/schemas/workflow/patterns.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/schemas/workflow/state.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/schemas/workflow/transition.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/__init__.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/a2a/__init__.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/a2a/client.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/communication/__init__.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/communication/email_service.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/communication/messaging_service.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/communication/sms_service.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/decisioning/__init__.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/decisioning/decision_engine.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/decisioning/expression_engine.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/decisioning/graph_evaluator.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/decisioning/models.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/decisioning/table_evaluator.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/encryption/__init__.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/encryption/field_encryption.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/gdpr/__init__.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/gdpr/deletion.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/integrations/__init__.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/integrations/auth_service.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/integrations/integration_service.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/integrations/mapping_service.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/integrations/models.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/integrations/registry.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/inventory/__init__.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/inventory/available_capabilities.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/inventory/cached_capabilities.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/inventory/loader.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/judgment/__init__.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/judgment/batch.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/judgment/calibration.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/judgment/confidence.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/judgment/contract.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/judgment/guards.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/judgment/models.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/judgment/providers/__init__.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/judgment/providers/fixture.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/judgment/providers/typesafe.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/llm/__init__.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/llm/base.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/llm/claude_provider.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/llm/gemini_provider.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/llm/groq_provider.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/llm/model_choice.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/llm/perplexity_provider.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/llm_service.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/media/DIRECTORY_STRUCTURE.md +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/media/MIGRATION_GUIDE.md +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/media/QUICKSTART.md +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/media/README.md +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/media/VIDEO_README.md +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/media/__init__.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/media/config_helpers.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/media/examples.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/media/image_generation.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/media/model_resolver.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/media/video_examples.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/media/video_generation.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/persistence/__init__.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/persistence/base.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/persistence/cache.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/persistence/instances.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/persistence/manager.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/persistence/notification.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/persistence/research.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/persistence/session.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/persistence/tracker.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/registry.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/runtime_cache/__init__.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/runtime_cache/invalidator.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/runtime_cache/user_context_cache.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/runtime_cache/valkey_client.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/runtime_cache/workflow_catalog_cache.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/security/__init__.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/security/oauth_service.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/vector/__init__.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/vector/auto_ingestion.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/vector/search.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/vector/tag_embedding.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/vendors/__init__.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/services/vendors/stripe_service.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/utils/__init__.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/utils/config.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/utils/decisioning/__init__.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/utils/decisioning/csv_parser.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/utils/decisioning/dmn_parser.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/utils/decisioning/yaml_exporter.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/utils/decisioning/yaml_parser.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/utils/helpers.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/utils/llm/chunk_aggregator.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/utils/llm/content_condenser.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/utils/llm/content_handler.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/utils/llm/hierarchical_summarizer.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/utils/serialization_helpers.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/utils/time_helpers.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/common/utils/validators.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/data/models/00_association_tables.yaml +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/data/models/messaging_models.yaml +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/data/models/notification_models.yaml +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/data/models/oauth_models.yaml +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/data/models/user_preferences_models.yaml +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/data/models/vector_db_models.yaml +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/documentation/DOCUMENTATION_INDEX.md +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/documentation/DOCUMENTATION_SUMMARY.md +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/documentation/PROGRESS_UPDATE.md +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/documentation/TIMBER_SESSION_API_REFERENCE.md +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/documentation/best_practices/01_model_design_patterns.md +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/documentation/best_practices/02_service_architecture.md +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/documentation/best_practices/03_data_fetching_strategies.md +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/documentation/best_practices/04_caching_strategies.md +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/documentation/best_practices/05_error_handling.md +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/documentation/best_practices/06_performance_optimization.md +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/documentation/best_practices/07_security_best_practices.md +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/documentation/design_guides/01_system_architecture.md +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/documentation/design_guides/02_config_driven_models.md +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/documentation/design_guides/03_persistence_layer.md +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/documentation/design_guides/04_vector_integration.md +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/documentation/design_guides/05_multi_app_support.md +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/documentation/how_to/01_getting_started.md +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/documentation/how_to/02_creating_models.md +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/documentation/how_to/03_using_services.md +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/documentation/how_to/04_financial_data_fetching.md +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/documentation/how_to/05_encryption_and_security.md +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/documentation/how_to/06_vector_search.md +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/documentation/how_to/07_gdpr_compliance.md +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/documentation/how_to/08_testing_guide.md +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/documentation/how_to/09_typed_judgment.md +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/modules/__init__.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/modules/config/custom_analysis.yaml +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/oak_config/README.md +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/oak_config/__init__.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/oak_config/bootstrap.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/oak_config/cache.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/oak_config/canopy_auth.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/oak_config/client.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/oak_config/env.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/oak_config/models.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/oak_config/specs.py +0 -0
- {timber_common-1.2.0 → timber_common-1.2.2}/oak_config/subscriber.py +0 -0
|
@@ -5,6 +5,35 @@ All notable changes to Timber will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.2.2] - 2026-10-24
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Reject malformed and unknown database filters instead of dropping their predicates.
|
|
13
|
+
- Require non-empty filter dictionaries for updates and deletes, and preserve null
|
|
14
|
+
filters as SQL `IS NULL` predicates so a scoped mutation cannot become table-wide.
|
|
15
|
+
|
|
16
|
+
## [1.2.1] - 2026-10-06
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- **`import common` failed on a fresh install with `No module named 'psycopg'`.**
|
|
21
|
+
SQLAlchemy 2.1 changed the driver a bare `postgresql://` URL selects from
|
|
22
|
+
psycopg2 to psycopg (v3). timber allows `sqlalchemy = "^2.0.36"`, which admits
|
|
23
|
+
2.1, and depends on `psycopg2-binary` only, and `db_utils` builds an engine at
|
|
24
|
+
import time. So any install without a lock file (a fresh venv, a domain
|
|
25
|
+
package's CI, a host-side script) broke on import. Services were unaffected only
|
|
26
|
+
because grove's lock holds SQLAlchemy 2.0.51; the first `poetry update` that
|
|
27
|
+
moved it would have broken grove at boot.
|
|
28
|
+
|
|
29
|
+
Every `create_engine` call now goes through `common.utils.db_url.with_pinned_driver`,
|
|
30
|
+
which turns `postgresql://` and `postgres://` into `postgresql+psycopg2://`.
|
|
31
|
+
`config.DATABASE_URL` is NOT rewritten, because `Config.validate` and callers in
|
|
32
|
+
other repos compare it against `postgresql://`. An AST test fails if a new
|
|
33
|
+
`create_engine` call skips the pin. Verified in a clean SQLAlchemy 2.1.3 venv
|
|
34
|
+
with no psycopg v3 installed: `import common` succeeds and the engine's driver is
|
|
35
|
+
psycopg2.
|
|
36
|
+
|
|
8
37
|
## [1.2.0] - 2026-10-06
|
|
9
38
|
|
|
10
39
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: timber-common
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.2
|
|
4
4
|
Summary: Configuration-driven persistence library with ml tools (finance related) config driven db model registration, llm model choice, automatic encryption, caching, vector search, and GDPR compliance for Python applications
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
Keywords: orm,sqlalchemy,persistence,vector-search,semantic-search,encryption,gdpr,yaml-config,postgres,redis,configuration-driven,data-modeling,machine-learning,embeddings
|
|
@@ -13,6 +13,7 @@ from sqlalchemy import create_engine, event, text
|
|
|
13
13
|
from sqlalchemy.orm import declarative_base
|
|
14
14
|
from sqlalchemy.orm import sessionmaker, Session, scoped_session
|
|
15
15
|
from sqlalchemy.pool import QueuePool
|
|
16
|
+
from common.utils.db_url import with_pinned_driver
|
|
16
17
|
from sqlalchemy.exc import OperationalError, DBAPIError
|
|
17
18
|
from contextlib import contextmanager
|
|
18
19
|
from typing import Optional, Generator
|
|
@@ -98,7 +99,7 @@ class DatabaseManager:
|
|
|
98
99
|
|
|
99
100
|
# Create engine with connection pooling
|
|
100
101
|
self._engine = create_engine(
|
|
101
|
-
database_url,
|
|
102
|
+
with_pinned_driver(database_url),
|
|
102
103
|
echo=echo,
|
|
103
104
|
poolclass=QueuePool,
|
|
104
105
|
pool_size=pool_size,
|
|
@@ -22,6 +22,7 @@ from sqlalchemy.exc import OperationalError, DBAPIError, IntegrityError
|
|
|
22
22
|
from sqlalchemy.pool import QueuePool
|
|
23
23
|
|
|
24
24
|
from common.utils.config import config
|
|
25
|
+
from common.utils.db_url import with_pinned_driver
|
|
25
26
|
|
|
26
27
|
logger = logging.getLogger(__name__)
|
|
27
28
|
|
|
@@ -81,7 +82,7 @@ class DBService:
|
|
|
81
82
|
try:
|
|
82
83
|
# Create engine with connection pooling
|
|
83
84
|
self._engine = create_engine(
|
|
84
|
-
db_url,
|
|
85
|
+
with_pinned_driver(db_url),
|
|
85
86
|
echo=config.DATABASE_ECHO,
|
|
86
87
|
poolclass=QueuePool,
|
|
87
88
|
pool_size=pool_config['pool_size'],
|
|
@@ -322,29 +323,18 @@ class DBService:
|
|
|
322
323
|
|
|
323
324
|
def _apply_filters(self, query, model_class: Type, filters: Dict[str, Any]):
|
|
324
325
|
"""Apply filters to query."""
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
return query
|
|
328
|
-
|
|
329
|
-
# Handle string filters (invalid input - return unfiltered)
|
|
330
|
-
if isinstance(filters, str):
|
|
331
|
-
logger.warning(f"Received string filters instead of dict: {filters}")
|
|
332
|
-
return query
|
|
333
|
-
|
|
334
|
-
# Handle non-dict filters
|
|
326
|
+
if filters is None:
|
|
327
|
+
filters = {}
|
|
335
328
|
if not isinstance(filters, dict):
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
329
|
+
raise ValueError("Filters must be a dict")
|
|
330
|
+
unknown = [field for field in filters if not hasattr(model_class, field)]
|
|
331
|
+
if unknown:
|
|
332
|
+
raise ValueError(f"Unknown filter fields: {', '.join(map(str, unknown))}")
|
|
340
333
|
for field, value in filters.items():
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
query = query.filter(getattr(model_class, field) == value)
|
|
344
|
-
else:
|
|
345
|
-
logger.warning(f"Model {model_class.__name__} has no field '{field}'")
|
|
334
|
+
# SQLAlchemy translates None to IS NULL; dropping it broadens scope.
|
|
335
|
+
query = query.filter(getattr(model_class, field) == value)
|
|
346
336
|
return query
|
|
347
|
-
|
|
337
|
+
|
|
348
338
|
def _apply_ordering(self, query, model_class: Type, order_by):
|
|
349
339
|
"""Apply ordering to a query.
|
|
350
340
|
|
|
@@ -461,8 +451,7 @@ class DBService:
|
|
|
461
451
|
logger.warning(f"Filters passed as string, using empty filters. Got: {filters}")
|
|
462
452
|
filters = {}
|
|
463
453
|
elif not isinstance(filters, dict):
|
|
464
|
-
|
|
465
|
-
filters = {}
|
|
454
|
+
raise ValueError("Filters must be a dict")
|
|
466
455
|
|
|
467
456
|
if include_relationships is None:
|
|
468
457
|
include_relationships = []
|
|
@@ -593,6 +582,9 @@ class DBService:
|
|
|
593
582
|
data={'status': 'completed'}
|
|
594
583
|
)
|
|
595
584
|
"""
|
|
585
|
+
if not isinstance(filters, dict) or not filters:
|
|
586
|
+
raise ValueError("Mutations require a non-empty filter dict")
|
|
587
|
+
|
|
596
588
|
model_class = self._get_model_class(model)
|
|
597
589
|
|
|
598
590
|
with self.session_scope() as session:
|
|
@@ -626,6 +618,9 @@ class DBService:
|
|
|
626
618
|
filters={'id': 'goal-123'}
|
|
627
619
|
)
|
|
628
620
|
"""
|
|
621
|
+
if not isinstance(filters, dict) or not filters:
|
|
622
|
+
raise ValueError("Mutations require a non-empty filter dict")
|
|
623
|
+
|
|
629
624
|
model_class = self._get_model_class(model)
|
|
630
625
|
|
|
631
626
|
with self.session_scope() as session:
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# timber/common/utils/db_url.py
|
|
2
|
+
"""Name the Postgres driver in every URL handed to ``create_engine``.
|
|
3
|
+
|
|
4
|
+
⚠️⚠️ SQLALCHEMY 2.1 CHANGED WHAT ``postgresql://`` MEANS. Under 2.0 a bare
|
|
5
|
+
``postgresql://`` URL selects psycopg2; under 2.1 it selects psycopg (v3).
|
|
6
|
+
timber declares ``sqlalchemy = "^2.0.36"``, which admits 2.1, and depends on
|
|
7
|
+
``psycopg2-binary`` only. So any install without a lock file (a fresh venv, a
|
|
8
|
+
domain package's CI, a host-side script) got SQLAlchemy 2.1.3 and failed on
|
|
9
|
+
``import common`` with ``No module named 'psycopg'``, because ``db_utils``
|
|
10
|
+
builds an engine at import time. Measured 2026-10-06 in a clean venv. The
|
|
11
|
+
services were unaffected only because grove's lock holds 2.0.51, so the first
|
|
12
|
+
``poetry update`` that moved SQLAlchemy would have broken grove at boot.
|
|
13
|
+
|
|
14
|
+
The fix is to say which driver we mean, at the boundary where the URL becomes
|
|
15
|
+
an engine, so the URL and the declared dependency can never disagree again.
|
|
16
|
+
``config.DATABASE_URL`` itself is NOT rewritten: ``Config.validate`` and callers
|
|
17
|
+
in other repos compare it against ``postgresql://``.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
__all__ = ["PG_DRIVER", "with_pinned_driver"]
|
|
23
|
+
|
|
24
|
+
#: The driver timber declares in pyproject. Change both together or neither.
|
|
25
|
+
PG_DRIVER = "psycopg2"
|
|
26
|
+
|
|
27
|
+
_BARE_SCHEMES = ("postgresql://", "postgres://")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def with_pinned_driver(url: str) -> str:
|
|
31
|
+
"""``postgresql://…`` to ``postgresql+psycopg2://…``; anything else unchanged.
|
|
32
|
+
|
|
33
|
+
A URL that already names a driver (``postgresql+psycopg://``) is the
|
|
34
|
+
caller's explicit choice and is left alone. So is SQLite.
|
|
35
|
+
"""
|
|
36
|
+
if not url:
|
|
37
|
+
return url
|
|
38
|
+
for scheme in _BARE_SCHEMES:
|
|
39
|
+
if url.startswith(scheme):
|
|
40
|
+
return f"postgresql+{PG_DRIVER}://" + url[len(scheme):]
|
|
41
|
+
return url
|
|
@@ -8,6 +8,7 @@ from sqlalchemy.orm import sessionmaker, Session, DeclarativeBase
|
|
|
8
8
|
from sqlalchemy.pool import StaticPool
|
|
9
9
|
|
|
10
10
|
from common.utils.config import config
|
|
11
|
+
from common.utils.db_url import with_pinned_driver
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
class Base(DeclarativeBase):
|
|
@@ -41,7 +42,7 @@ class DatabaseManager:
|
|
|
41
42
|
if ":memory:" in self.database_url:
|
|
42
43
|
engine_kwargs["poolclass"] = StaticPool
|
|
43
44
|
|
|
44
|
-
self.engine = create_engine(self.database_url, **engine_kwargs)
|
|
45
|
+
self.engine = create_engine(with_pinned_driver(self.database_url), **engine_kwargs)
|
|
45
46
|
self.SessionLocal = sessionmaker(
|
|
46
47
|
autocommit=False,
|
|
47
48
|
autoflush=False,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "timber-common"
|
|
3
|
-
version = "1.2.
|
|
3
|
+
version = "1.2.2"
|
|
4
4
|
description = "Configuration-driven persistence library with ml tools (finance related) config driven db model registration, llm model choice, automatic encryption, caching, vector search, and GDPR compliance for Python applications"
|
|
5
5
|
authors = ["Pumulo Sikaneta <pumulo@gmail.com>"]
|
|
6
6
|
maintainers = ["Pumulo Sikaneta <pumulo@gmail.com>"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{timber_common-1.2.0 → timber_common-1.2.2}/common/services/communication/messaging_service.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{timber_common-1.2.0 → timber_common-1.2.2}/common/services/decisioning/expression_engine.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{timber_common-1.2.0 → timber_common-1.2.2}/common/services/integrations/integration_service.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{timber_common-1.2.0 → timber_common-1.2.2}/common/services/inventory/available_capabilities.py
RENAMED
|
File without changes
|
{timber_common-1.2.0 → timber_common-1.2.2}/common/services/inventory/cached_capabilities.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{timber_common-1.2.0 → timber_common-1.2.2}/common/services/runtime_cache/user_context_cache.py
RENAMED
|
File without changes
|
|
File without changes
|
{timber_common-1.2.0 → timber_common-1.2.2}/common/services/runtime_cache/workflow_catalog_cache.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{timber_common-1.2.0 → timber_common-1.2.2}/documentation/best_practices/01_model_design_patterns.md
RENAMED
|
File without changes
|
{timber_common-1.2.0 → timber_common-1.2.2}/documentation/best_practices/02_service_architecture.md
RENAMED
|
File without changes
|
|
File without changes
|
{timber_common-1.2.0 → timber_common-1.2.2}/documentation/best_practices/04_caching_strategies.md
RENAMED
|
File without changes
|
{timber_common-1.2.0 → timber_common-1.2.2}/documentation/best_practices/05_error_handling.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{timber_common-1.2.0 → timber_common-1.2.2}/documentation/design_guides/01_system_architecture.md
RENAMED
|
File without changes
|
{timber_common-1.2.0 → timber_common-1.2.2}/documentation/design_guides/02_config_driven_models.md
RENAMED
|
File without changes
|
{timber_common-1.2.0 → timber_common-1.2.2}/documentation/design_guides/03_persistence_layer.md
RENAMED
|
File without changes
|
{timber_common-1.2.0 → timber_common-1.2.2}/documentation/design_guides/04_vector_integration.md
RENAMED
|
File without changes
|
{timber_common-1.2.0 → timber_common-1.2.2}/documentation/design_guides/05_multi_app_support.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{timber_common-1.2.0 → timber_common-1.2.2}/documentation/how_to/04_financial_data_fetching.md
RENAMED
|
File without changes
|
{timber_common-1.2.0 → timber_common-1.2.2}/documentation/how_to/05_encryption_and_security.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|