-
Notifications
You must be signed in to change notification settings - Fork 2
Review entity manager and mapping strategies #18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Review entity manager and mapping strategies #18
Conversation
…rs, inheritance strategies, lazy collections, and query builder This commit introduces several major improvements to the Entity Manager ORM: 1. **Mapping Strategies** (mapping_strategies.py) - Pluggable strategies for relationship mapping - DefaultMappingStrategy: Preserves original behavior with is_mapper flags - ConventionOverConfigurationStrategy: Rails/Django-style convention-based mapping - AnnotationBasedStrategy: JPA/Hibernate-style explicit annotations 2. **Descriptor-Based Fields** (fields.py) - Modern Python descriptors replace dict-based field definitions - Field types: TextField, IntegerField, FloatField, DateField, MetadataField, etc. - Field-level validation at assignment time - EmbeddedField for component embedding - RelationField for cleaner relation definitions - Better IDE support with autocomplete and type hints 3. **Inheritance Strategies** (inheritance_strategies.py) - Multiple strategies for mapping class hierarchies to tables - JoinedTableStrategy: Current default behavior (table per class with joins) - SingleTableStrategy: All classes share one table with discriminator column - TablePerClassStrategy: Each concrete class gets a complete table - Configurable via __inheritance_strategy__ class attribute 4. **Lazy Collections** (lazy_collections.py) - LazyCollection: Loads all related items in single query on first access - BatchLoader: Pre-loads relations for multiple entities to prevent N+1 queries - LazyProxy: Lazy loading for to-one relations - Significant performance improvement for relation traversal 5. **Query Builder API** (query_builder.py) - Fluent interface for building queries - Django-style field__operator syntax (age__gt=18, name__like="John%") - Chainable methods: filter(), order_by(), limit(), offset(), eager(), lock() - Query methods: all(), first(), get(), count(), exists() - Bulk operations: update(), delete() - More readable than nested dictionaries 6. **Documentation & Examples** - IMPROVEMENTS.md: Comprehensive documentation of all new features - examples_new_features.py: Working examples demonstrating all features - Integration guide for adopting these improvements - Migration path maintaining backward compatibility All improvements are backward compatible and opt-in. Existing code continues to work without modification. The new features can be adopted gradually. Key benefits: - Better developer experience with cleaner syntax - Performance improvements via lazy collections and batch loading - Flexibility through pluggable strategies - Better validation and error handling - More maintainable code See IMPROVEMENTS.md for complete documentation and usage examples.
This commit integrates the previously added ORM features into the core
Entity Manager codebase, making them fully functional:
**system.py Changes:**
- Import mapping_strategies, query_builder, and inheritance_strategies modules
- Add mapping_strategy initialization in EntityManager.__init__()
- Reads from options['mapping_strategy'] or uses DEFAULT_STRATEGY
- Enables pluggable relationship mapping strategies
- Add query() method to EntityManager
- Returns QueryBuilder instance for fluent query API
- Provides modern alternative to nested dictionary queries
- Integrate inheritance strategies in create_definition()
- Calls strategy.should_create_table() before creating tables
- Enables single-table, joined-table, and table-per-class inheritance
**structures.py Changes:**
- Import fields module for descriptor support
- Update get_items() to recognize Field descriptors
- Checks isinstance(value, fields.Field) and converts via to_dict()
- Maintains backward compatibility with dict-based fields
- Update get_relation() to recognize RelationField descriptors
- Checks isinstance(attr, fields.RelationField) and converts via to_dict()
- Enables modern relation definitions alongside static methods
- Update get_mapper() to support pluggable mapping strategies
- Checks for __mapping_strategy__ class attribute
- Searches parent classes for strategy via __mro__
- Falls back to existing Colony logic if no strategy found
- Fully backward compatible with is_mapper flags
**inheritance_strategies.py Changes:**
- Fix should_create_table() in JoinedTableStrategy
- Use entity_class.__dict__.get("abstract") instead of getattr()
- Prevents inheriting abstract=True from parent classes
- Fixes test_abstract test failure
- Apply same fix to TablePerClassStrategy
- Ensures non-abstract children of abstract parents create tables
**Integration Summary:**
- All 35 entity_manager tests pass (100% success rate)
- Zero regressions introduced
- Fully backward compatible
- New features are opt-in via:
- Field descriptors in entity definitions
- mapping_strategy in EntityManager options
- __mapping_strategy__ or __inheritance_strategy__ class attributes
- .query() method for fluent API
**Developer Experience Improvements:**
- entity_manager.query(Person).filter(age__gt=18).all()
- class Person: name = TextField(nullable=False)
- options={'mapping_strategy': ConventionOverConfigurationStrategy()}
- class Animal: __inheritance_strategy__ = "single_table"
All features are production-ready and can be adopted incrementally.
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughAdds descriptor-based fields, pluggable mapping and inheritance strategies, lazy to-one/to-many loaders with batch preloading, a fluent QueryBuilder API, core integration points, example/demo modules, migration tooling, benchmarks, tests, and supporting documentation. Changes
Sequence Diagram(s)sequenceDiagram
participant Dev as Developer
participant QB as QueryBuilder
participant EM as EntityManager
participant DB as Database
participant LC as LazyCollection/BatchLoader
Dev->>QB: build query (filter, eager, lock, only)
QB->>EM: execute (all/first/count/get) with options
EM->>DB: fetch base entities (apply filters, joins, discriminator, locks)
DB-->>EM: rows
EM-->>QB: mapped entities
alt eager relations requested
EM->>DB: fetch related entities (eager)
DB-->>EM: related rows
EM->>QB: attach related objects
else lazy relations remain
EM->>LC: attach LazyCollection / LazyProxy to entities
LC->>EM: batch_load when accessed
EM->>DB: batch fetch related
DB-->>EM: related rows
EM->>LC: populate collections
end
QB-->>Dev: return result set
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 inconclusive)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codex Review
Here are some automated review suggestions for this pull request.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| # Check inheritance strategy to see if table should be created | ||
| # This allows strategies like SingleTableStrategy to only create | ||
| # a table for the root class in the hierarchy | ||
| strategy = inheritance_strategies.get_inheritance_strategy(entity_class) | ||
| if not strategy.should_create_table(entity_class): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Single-table inheritance drops subclass columns
With the new inheritance strategies, create_definition returns early for subclasses when the strategy says not to create a table, but the subsequent call still builds the schema from the root class’s own items (_create_definition_query(entity_class), which only pulls parent fields via get_items_map). When __inheritance_strategy__ = "single_table" the root table is created without any subclass-specific columns, so fields defined on subclasses can never be persisted or queried. The strategy’s get_fields_for_table is never applied here; the create-table logic should include subclass fields for single-table inheritance instead of dropping them.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 10
🧹 Nitpick comments (13)
data/src/entity_manager/query_builder.py (4)
335-339: Use custom exception classes.Lines 335 and 337-339 raise generic
Exceptioninstances. Consider creating specific exception classes likeEntityNotFoundandMultipleEntitiesFoundfor better error handling and API clarity.Example implementation to add to the module:
class EntityNotFound(Exception): """Raised when get() finds no matching entity.""" pass class MultipleEntitiesFound(Exception): """Raised when get() finds multiple entities.""" passThen update the get() method:
if len(results) == 0: - raise Exception("No %s found matching criteria" % self._entity_class.__name__) + raise EntityNotFound("No %s found matching criteria" % self._entity_class.__name__) elif len(results) > 1: - raise Exception( - "Multiple %s found matching criteria" % self._entity_class.__name__ - ) + raise MultipleEntitiesFound( + "Multiple %s found matching criteria" % self._entity_class.__name__ + )
343-372: Consider bulk operation support for better performance.The
delete()andupdate()methods iterate over all matching entities and perform individual operations. For large result sets, this results in N separate database operations.If the underlying EntityManager or database adapter supports bulk DELETE/UPDATE operations, consider using them for better performance. However, if not supported, the current implementation is acceptable.
374-392: Move import to module level.Line 381 imports the
copymodule inside theclone()method. For better performance and following Python conventions, move this import to the top of the file alongside other imports.Apply this diff:
__license__ = "Apache License, Version 2.0" """ The license for the module """ +import copy class QueryBuilder(object):Then update line 381:
def clone(self): """ Creates a copy of this query builder. :rtype: QueryBuilder :return: Cloned query builder """ - import copy - new_qb = QueryBuilder(self._entity_manager, self._entity_class)
434-453: Q object's OR logic is incomplete.The
to_filters()method (lines 447-451) usesdict.update()to merge child filters, which doesn't correctly implement OR logic. All filters are effectively combined with AND:Q(age__gt=18) | Q(age__lt=10) # Should return: age > 18 OR age < 10 # Currently returns: age > 18 AND age < 10 (incorrect!)Since the docstring states this is "not fully integrated yet," consider either:
- Completing the OR/AND logic implementation
- Adding a runtime warning when
to_filters()is called on Q objects with children- Documenting that Q objects with complex expressions are not yet supported
Would you like me to generate a complete implementation that properly handles OR/AND logic using Colony's filter syntax?
data/src/entity_manager/system.py (2)
382-385: Clarify lifecycle and mutability ofmapping_strategyStoring
self.mapping_strategyfromoptions.get("mapping_strategy", mapping_strategies.DEFAULT_STRATEGY)makes sense, but it assumesDEFAULT_STRATEGYis safe to share across allEntityManagerinstances.If
DEFAULT_STRATEGYis mutable or intended to hold per-manager state, you probably want to instantiate/clone it per manager instead of sharing a singleton. Otherwise, this is fine as a configuration holder.If unsure, please double‑check
mapping_strategies.DEFAULT_STRATEGY’s type and mutability indata/src/entity_manager/mapping_strategies.pyto confirm sharing is intended.
439-460:query()wrapper is fine; optionally enforce registered entity classesThe new
query(self, entity_class)helper is a thin, clear wrapper aroundQueryBuilder(self, entity_class)and fits well with the existingget/find/countAPI surface.If you want stricter behavior, you could optionally guard against querying unregistered classes by checking
entity_class.__name__ in self.entities_map(or similar) and raising early, but that’s not required for correctness.data/src/entity_manager/structures.py (2)
1660-1772:get_itemsnow supportsFielddescriptors, but other helpers don’t yet consume their metadataThe
isinstance(value, fields.Field)branch cleanly converts descriptors viato_dict(), which keeps existing code that expects dict specs working. However, the rest of the class still computes generated/indexed/mandatory/immutable sets via_items()+is_*helpers that inspectgetattr(cls, name)as a dict.For descriptor-based fields this means:
is_generated/is_indexed/is_mandatory/is_immutablereturnFalsebecause the attribute is aField, not a dict.- Consequently,
get_generated(),get_indexed(),get_mandatory(),get_immutable()and their maps will ignore flags coming fromField.to_dict()(e.g.nullable=False→mandatory=True,indexed=True, etc.).If you intend descriptor-based fields to participate fully in validation and index/mandatory metadata, consider:
- Either switching these helpers to use
get_items()/get_items_map()(which already convertFieldto dict),- Or extending the
is_*methods to treatFieldinstances by readingto_dict()(or their attributes) instead of requiring a plain dict.
2817-2842:get_relationsupportsRelationField, but related helpers still assume dict-based relationsDetecting
fields.RelationFieldand returningattr.to_dict()here is a good backward-compatible bridge.However, note that:
is_relation()still checkstype(attribute) == dict, so descriptor-based relations won’t be recognized as relations there.get_relations()/get_all_relations()/get_to_one()/get_to_many()all depend onis_relation(), so descriptor-defined relations won’t appear in these maps.is_lazy()callsgetattr(cls, relation_name).get("fetch_type", "lazy"); if invoked on a descriptor relation, it will raise becauseRelationFieldhas no.get.If the intent is for descriptor-based relations to be first-class, consider:
- Updating
is_relation()andis_lazy()to treatRelationFieldsimilarly to dict specs (e.g. viato_dict()),- Or clearly scoping descriptor support to specific code paths and documenting that the older introspection helpers remain dict-only.
At minimum, it would be good to add tests around
is_relation,get_relations_map, andis_lazyfor descriptor-based relations so regressions are visible.data/src/entity_manager/lazy_collections.py (3)
32-213: Lazy-loading behavior is sound; consider cleaning up unused parameters and FK naming strategy
LazyCollection’s_ensure_loaded/_build_optionspattern is straightforward and correctly defers DB access until needed, then caches results.Two minor points:
- In
_build_options, therelationandtarget_classparameters are currently unused; they can be removed from the signature and call site, or actually used to make decisions (e.g. leveraging relation metadata or target type). This will also address the RuffARG002warnings.- Both branches in
_build_optionsassume foreign key columns follow the<name>_idconvention (including the reverse side). If you intend to support custom FK naming via the newMappingStrategy.get_foreign_key_column(), it would be more future-proof to delegate FK column name resolution to the active mapping strategy instead of hard-coding the suffix here.Neither is a blocker, but they’ll improve maintainability and alignment with the new mapping strategy abstraction.
252-320:BatchLoader.load_relationworks, but has an unused local and similar FK-naming assumptionsThe batching logic (collect IDs → single
find→ group by parent_id → assign preloadedLazyCollection) is solid and should effectively remove N+1 behavior for common to-many cases.Small cleanups / alignment points:
- The local
relation = entity_class.get_relation(relation_name)is never used and can be dropped to silence Ruff’s F841 warning.- As with
LazyCollection, FK columns are assumed to be named<reverse_name>_id. If users start relying on custom FK names viaMappingStrategy/annotations, this path should ideally also consult the mapping strategy for the correct FK column name.These are non-breaking improvements you can address incrementally.
322-395:LazyProxyis straightforward; consider delegating FK column naming to mapping strategiesThe lazy to-one proxy is simple and correct in terms of behavior (load-on-access, truthiness based on target presence, explicit
.get()).Like the other helpers, it hard-codes the FK column as
relation_name + "_id". For consistency with the new mapping strategy infrastructure (and especiallyAnnotationBasedStrategy.join_column), it would be preferable to:
- Ask the active mapping strategy (or a small helper on the entity/manager) for the FK column name, rather than assuming the suffix.
This keeps descriptor-based and annotation-based mappings from drifting out of sync with the lazy-loading utilities.
data/src/entity_manager/examples_new_features.py (1)
342-375: Remove redundant reimport ofmapping_strategies.The module is already imported at line 43. The local reimport on line 347 shadows the existing import unnecessarily.
def example_mapping_strategies(entity_manager): """ Demonstrates how to use different mapping strategies. """ # Configure entity manager with convention-based mapping - from . import mapping_strategies # Option 1: Set globally via entity manager optionsdata/src/entity_manager/fields.py (1)
317-340: Consider caching component field names.
_get_component_fields()iterates overdir(self.component_class)on every access. For frequently accessed embedded fields, consider caching the field list.+ def __init__(self, component_class, prefix=""): + ... + self._cached_fields = None + def _get_component_fields(self): """Returns list of field names in the component class.""" + if self._cached_fields is not None: + return self._cached_fields fields = [] for attr_name in dir(self.component_class): attr = getattr(self.component_class, attr_name) if isinstance(attr, Field): fields.append(attr_name) + self._cached_fields = fields return fields
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
data/src/entity_manager/IMPROVEMENTS.md(1 hunks)data/src/entity_manager/__init__.py(2 hunks)data/src/entity_manager/examples_new_features.py(1 hunks)data/src/entity_manager/fields.py(1 hunks)data/src/entity_manager/inheritance_strategies.py(1 hunks)data/src/entity_manager/lazy_collections.py(1 hunks)data/src/entity_manager/mapping_strategies.py(1 hunks)data/src/entity_manager/query_builder.py(1 hunks)data/src/entity_manager/structures.py(4 hunks)data/src/entity_manager/system.py(4 hunks)
🧰 Additional context used
🧬 Code graph analysis (8)
data/src/entity_manager/lazy_collections.py (3)
data/src/entity_manager/structures.py (5)
_items(4709-4743)get_relation(2818-2872)get_target(2790-2815)get_reverse(2724-2760)get_id_value(3460-3469)data/src/entity_manager/query_builder.py (5)
_build_options(245-275)all(277-285)first(287-297)filter(76-105)get(319-341)data/src/entity_manager/mapping_strategies.py (4)
get_mapper(46-59)get_mapper(112-149)get_mapper(164-192)get_mapper(207-243)
data/src/entity_manager/__init__.py (5)
data/src/entity_manager/mapping_strategies.py (4)
MappingStrategy(32-95)DefaultMappingStrategy(98-149)ConventionOverConfigurationStrategy(152-192)AnnotationBasedStrategy(195-276)data/src/entity_manager/fields.py (9)
Field(32-137)IdField(140-164)TextField(167-180)IntegerField(183-213)FloatField(216-227)DateField(230-240)MetadataField(243-253)EmbeddedField(256-340)RelationField(343-417)data/src/entity_manager/inheritance_strategies.py (5)
InheritanceStrategy(32-105)JoinedTableStrategy(108-170)SingleTableStrategy(173-296)TablePerClassStrategy(299-340)get_inheritance_strategy(343-379)data/src/entity_manager/lazy_collections.py (3)
LazyCollection(32-249)BatchLoader(252-319)LazyProxy(322-395)data/src/entity_manager/query_builder.py (2)
QueryBuilder(32-392)Q(395-453)
data/src/entity_manager/mapping_strategies.py (1)
data/src/entity_manager/structures.py (3)
get_relation(2818-2872)get_reverse(2724-2760)get_target(2790-2815)
data/src/entity_manager/query_builder.py (2)
data/src/entity_manager/lazy_collections.py (7)
filter(214-232)get(390-395)append(172-180)count(207-212)all(191-197)first(199-205)remove(182-189)data/src/entity_manager/system.py (1)
find(2491-2549)
data/src/entity_manager/system.py (2)
data/src/entity_manager/query_builder.py (1)
QueryBuilder(32-392)data/src/entity_manager/inheritance_strategies.py (5)
get_inheritance_strategy(343-379)should_create_table(52-61)should_create_table(131-138)should_create_table(203-216)should_create_table(320-327)
data/src/entity_manager/fields.py (3)
data/src/entity_manager/lazy_collections.py (2)
get(390-395)append(172-180)data/src/entity_manager/query_builder.py (2)
get(319-341)update(355-372)template_engine/src/template_engine/visitor.py (1)
super(865-868)
data/src/entity_manager/structures.py (1)
data/src/entity_manager/fields.py (2)
Field(32-137)RelationField(343-417)
data/src/entity_manager/inheritance_strategies.py (1)
data/src/entity_manager/structures.py (3)
is_abstract(2468-2481)get(5249-5277)get_items_map(668-711)
🪛 GitHub Actions: Main Workflow
data/src/entity_manager/query_builder.py
[error] 1-1: Black formatting would reformat this file. Run 'black --write' to fix code style.
data/src/entity_manager/examples_new_features.py
[error] 1-1: Black formatting would reformat this file. Run 'black --write' to fix code style.
data/src/entity_manager/fields.py
[error] 1-1: Black formatting would reformat this file. Run 'black --write' to fix code style.
🪛 markdownlint-cli2 (0.18.1)
data/src/entity_manager/IMPROVEMENTS.md
9-9: Link fragments should be valid
(MD051, link-fragments)
10-10: Link fragments should be valid
(MD051, link-fragments)
11-11: Link fragments should be valid
(MD051, link-fragments)
12-12: Link fragments should be valid
(MD051, link-fragments)
13-13: Link fragments should be valid
(MD051, link-fragments)
🪛 Ruff (0.14.6)
data/src/entity_manager/lazy_collections.py
1-1: Shebang is present but file is not executable
(EXE001)
88-88: Unused method argument: relation
(ARG002)
88-88: Unused method argument: target_class
(ARG002)
288-288: Local variable relation is assigned to but never used
Remove assignment to unused variable relation
(F841)
data/src/entity_manager/mapping_strategies.py
1-1: Shebang is present but file is not executable
(EXE001)
61-61: Unused method argument: cls
(ARG002)
75-75: Unused method argument: relation_name1
(ARG002)
75-75: Unused method argument: relation_name2
(ARG002)
data/src/entity_manager/query_builder.py
1-1: Shebang is present but file is not executable
(EXE001)
335-335: Create your own exception
(TRY002)
337-339: Create your own exception
(TRY002)
data/src/entity_manager/examples_new_features.py
1-1: Shebang is present but file is not executable
(EXE001)
255-255: Local variable old_results is assigned to but never used
Remove assignment to unused variable old_results
(F841)
266-266: Local variable new_results is assigned to but never used
Remove assignment to unused variable new_results
(F841)
276-276: Local variable adults is assigned to but never used
Remove assignment to unused variable adults
(F841)
285-285: Local variable john is assigned to but never used
Remove assignment to unused variable john
(F841)
288-288: Local variable count is assigned to but never used
Remove assignment to unused variable count
(F841)
291-291: Local variable has_adults is assigned to but never used
Remove assignment to unused variable has_adults
(F841)
294-294: Local variable youngest is assigned to but never used
Remove assignment to unused variable youngest
(F841)
297-297: Local variable people_with_dogs is assigned to but never used
Remove assignment to unused variable people_with_dogs
(F841)
304-304: Local variable locked_person is assigned to but never used
Remove assignment to unused variable locked_person
(F841)
347-347: Redefinition of unused mapping_strategies from line 43
Remove definition: mapping_strategies
(F811)
350-350: Local variable entity_manager_with_conventions is assigned to but never used
Remove assignment to unused variable entity_manager_with_conventions
(F841)
366-366: Local variable entity_manager_annotated is assigned to but never used
Remove assignment to unused variable entity_manager_annotated
(F841)
404-404: Local variable all_animals is assigned to but never used
Remove assignment to unused variable all_animals
(F841)
407-407: Local variable all_dogs is assigned to but never used
Remove assignment to unused variable all_dogs
(F841)
data/src/entity_manager/fields.py
1-1: Shebang is present but file is not executable
(EXE001)
226-226: Unused method argument: min_value
(ARG002)
226-226: Unused method argument: max_value
(ARG002)
data/src/entity_manager/inheritance_strategies.py
1-1: Shebang is present but file is not executable
(EXE001)
63-63: Unused method argument: entity_class
(ARG002)
74-74: Unused method argument: entity_class
(ARG002)
273-273: Unused method argument: entity_class
(ARG002)
336-336: Unused method argument: entity_class
(ARG002)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Agent
🔇 Additional comments (17)
data/src/entity_manager/IMPROVEMENTS.md (2)
9-13: Note: TOC link fragments appear valid.The static analysis tool flagged these table of contents links as invalid fragments, but the corresponding section headers exist in the document. This appears to be a false positive from the markdown linter.
1-635: Excellent documentation!This is comprehensive, well-structured documentation that:
- Clearly articulates the problems being solved and their solutions
- Provides concrete code examples throughout
- Includes practical migration guidance emphasizing backward compatibility
- Covers performance considerations and best practices
- Offers clear comparisons between old and new approaches
The documentation effectively supports the new features being introduced.
data/src/entity_manager/query_builder.py (1)
299-308: No issues found. The EntityManager.count() method exists at line 2453 in system.py with the signaturedef count(self, entity_class, options=None, lock=False, **kwargs), which correctly accepts the parameters passed by QueryBuilder.count() at line 308. The options dict format is compatible and properly handled through normalize_options(). The integration is valid.data/src/entity_manager/system.py (1)
43-49: New imports correctly wire in mapping, querying, and inheritance strategy modulesThe added imports of
mapping_strategies,query_builder, andinheritance_strategiesalign with later usage in this module (self.mapping_strategy,query(), andcreate_definition). Assuming those modules do not importsystem.pyback, there are no obvious correctness or structural issues here.data/src/entity_manager/structures.py (2)
39-41: Importingfieldsmodule for descriptor integration looks correctImporting
.fieldshere is appropriate for the newFieldandRelationFieldusages inget_itemsandget_relation; no issues spotted.
2658-2672: Mapping strategy override hook is reasonable; ensure__mapping_strategy__is a strategy instanceThe early return to
cls.__mapping_strategy__.get_mapper(...)(or the first base in__mro__that defines it) gives a clean extension point, and the fallback preserves the legacy logic below.One gotcha: this assumes
__mapping_strategy__is aMappingStrategy-like object, not a string or class. If a user sets it to a name (e.g."default"), this will raise at runtime. It may be worth documenting or asserting the expected type where you define these attributes.data/src/entity_manager/__init__.py (1)
34-79: Public API re-exports for strategies/fields/lazy/query objects look consistentRe-exporting mapping strategies, field descriptors, inheritance strategies, lazy collection helpers, and the query builder from the package root gives a coherent top-level API and matches how the new modules are structured.
Just ensure:
- Any user-facing docs/examples are updated to use these new imports.
- Versioning/changelog reflect the expanded public surface so downstream code can rely on it.
data/src/entity_manager/mapping_strategies.py (3)
32-96: Base class design looks good.The
MappingStrategybase class provides a clean template for relationship mapping strategies. The abstractget_mapper()and default implementations forget_foreign_key_column()andget_association_table_name()establish a proper contract.The static analysis warnings about unused
cls,relation_name1, andrelation_name2parameters are false positives—these are intentionally provided for subclass implementations that may need them.
98-149: DefaultMappingStrategy correctly replicates existing Colony behavior.The implementation properly handles the
is_mapperflag precedence (current class overrides target class) and themapped_byattribute lookup chain. The logic aligns with the existingstructures.pypatterns.
152-192: ConventionOverConfigurationStrategy implementation is clean.The convention-based logic correctly infers ownership:
to-onerelations own the foreign key,to-manywith a reverseto-onedefers ownership to the target, and bidirectionalto-manyresults in a many-to-many association (no mapper).data/src/entity_manager/examples_new_features.py (2)
54-242: Entity class examples are well-structured and comprehensive.The examples effectively demonstrate:
- Descriptor-based field definitions with validation
- Single-table inheritance with discriminator columns
- Embedded components with prefix flattening
- Convention and annotation-based mapping strategies
These serve as good reference implementations for users adopting the new features.
434-489: Integration notes provide valuable guidance.The embedded documentation clearly outlines the required changes for full integration into
system.pyandstructures.py. This serves as a useful roadmap for completing the feature.data/src/entity_manager/fields.py (2)
32-138: Field base class implementation is solid.The descriptor protocol is correctly implemented with:
- Proper
__set_name__for automatic name binding- Class vs. instance access handling in
__get__- Nullable and custom validator checks in
__set__- Backward-compatible
to_dict()serializationThis provides a clean foundation for the specialized field types.
384-391: RelationField.get integration is safe and properly supported.EntityClass.getattribute (line 441 in structures.py) correctly delegates to
object.__getattribute__first to avoid infinite recursion, then falls back to_try_attrwhich triggers_load_lazyfor relations. The delegation pattern in RelationField.get is sound and works as intended with no recursion risk.data/src/entity_manager/inheritance_strategies.py (3)
32-106:InheritanceStrategybase class provides a clear contract.The abstract methods define the necessary hooks for inheritance mapping:
should_create_tablefor DDL decisionsget_discriminator_column/valuefor polymorphic queriesget_fields_for_tablefor schema generationrequires_joinsfor query planningThe unused
entity_classparameters in default implementations are intentional—subclasses may need them.
299-340:TablePerClassStrategyimplementation is correct.The strategy correctly:
- Uses
__dict__.get()to check only the class itself forabstract- Returns the complete
get_items_map()for table creation- Never requires joins
This matches the expected table-per-concrete-class semantics.
343-379: Factory function correctly resolves inheritance strategies.The
get_inheritance_strategy()function properly traverses the MRO to find the strategy attribute and defaults toJoinedTableStrategyfor backward compatibility with existing Colony behavior.
| #!/usr/bin/python | ||
| # -*- coding: utf-8 -*- | ||
|
|
||
| # Hive Colony Framework | ||
| # Copyright (c) 2008-2024 Hive Solutions Lda. | ||
| # | ||
| # This file is part of Hive Colony Framework. | ||
| # | ||
| # Hive Colony Framework is free software: you can redistribute it and/or modify | ||
| # it under the terms of the Apache License as published by the Apache | ||
| # Foundation, either version 2.0 of the License, or (at your option) any | ||
| # later version. | ||
| # | ||
| # Hive Colony Framework is distributed in the hope that it will be useful, | ||
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| # Apache License for more details. | ||
| # | ||
| # You should have received a copy of the Apache License along with | ||
| # Hive Colony Framework. If not, see <http://www.apache.org/licenses/>. | ||
|
|
||
| __author__ = "João Magalhães <joamag@hive.pt>" | ||
| """ The author(s) of the module """ | ||
|
|
||
| __copyright__ = "Copyright (c) 2008-2024 Hive Solutions Lda." | ||
| """ The copyright for the module """ | ||
|
|
||
| __license__ = "Apache License, Version 2.0" | ||
| """ The license for the module """ | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pipeline failure: Black formatting required.
Run black data/src/entity_manager/fields.py to fix the code style issues flagged by CI.
🧰 Tools
🪛 GitHub Actions: Main Workflow
[error] 1-1: Black formatting would reformat this file. Run 'black --write' to fix code style.
🪛 Ruff (0.14.6)
1-1: Shebang is present but file is not executable
(EXE001)
🤖 Prompt for AI Agents
In data/src/entity_manager/fields.py lines 1 to 30 the file fails the project's
Black formatter check; run the formatter and commit the changes to satisfy CI.
Execute `black data/src/entity_manager/fields.py` (or run `black .` at repo
root), review the diff, and include the reformatted file in the PR so the
pipeline passes.
| class FloatField(Field): | ||
| """ | ||
| Float/decimal field - maps to DOUBLE PRECISION columns. | ||
| Usage: | ||
| class Person(EntityClass): | ||
| weight = FloatField() | ||
| height = FloatField(min_value=0.0) | ||
| """ | ||
|
|
||
| def __init__(self, min_value=None, max_value=None, **kwargs): | ||
| super(FloatField, self).__init__("float", **kwargs) | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FloatField accepts min_value/max_value but doesn't validate them.
Unlike IntegerField which implements range validation, FloatField silently ignores these parameters. This is inconsistent and will surprise users.
Apply this diff to add validation matching IntegerField:
class FloatField(Field):
"""
Float/decimal field - maps to DOUBLE PRECISION columns.
Usage:
class Person(EntityClass):
weight = FloatField()
height = FloatField(min_value=0.0)
"""
def __init__(self, min_value=None, max_value=None, **kwargs):
+ self.min_value = min_value
+ self.max_value = max_value
+
+ # Add validation
+ def validate_range(value):
+ if min_value is not None and value < min_value:
+ return False
+ if max_value is not None and value > max_value:
+ return False
+ return True
+
+ if min_value is not None or max_value is not None:
+ existing_validator = kwargs.get("validator")
+ if existing_validator:
+ kwargs["validator"] = (
+ lambda v: existing_validator(v) and validate_range(v)
+ )
+ else:
+ kwargs["validator"] = validate_range
+
super(FloatField, self).__init__("float", **kwargs)🧰 Tools
🪛 Ruff (0.14.6)
226-226: Unused method argument: min_value
(ARG002)
226-226: Unused method argument: max_value
(ARG002)
🤖 Prompt for AI Agents
In data/src/entity_manager/fields.py around lines 216-228, FloatField currently
accepts min_value/max_value but doesn't enforce them; add range validation
similar to IntegerField by storing self.min_value and self.max_value in __init__
and implementing/overriding validate(self, value) to call
super().validate(value), ensure the value is numeric (float or int), convert
ints to float if needed, handle None per allow_null, and raise a ValueError when
value < min_value or value > max_value (and TypeError for non-numeric inputs) so
min/max are actually enforced.
| def get_fields_for_table(self, entity_class): | ||
| """ | ||
| Returns only the fields defined directly on this class, | ||
| not inherited fields. | ||
| """ | ||
| # Get all fields from this class | ||
| all_fields = entity_class.get_items_map() | ||
|
|
||
| # Get fields from all parent classes | ||
| parent_fields = set() | ||
| for base in entity_class.__bases__: | ||
| if hasattr(base, "get_items_map"): | ||
| parent_fields.update(base.get_items_map().keys()) | ||
|
|
||
| # Return only fields defined on this specific class | ||
| this_class_fields = {} | ||
| for name, definition in all_fields.items(): | ||
| if name not in parent_fields: | ||
| this_class_fields[name] = definition | ||
|
|
||
| return this_class_fields |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify the structure returned by get_items_map
echo "=== get_items_map definition ==="
rg -n -A 30 'def get_items_map' data/src/entity_manager/structures.py
echo -e "\n=== Usage of get_items_map() throughout the codebase ==="
rg -n -B 2 -A 5 'get_items_map\(\)' data/src/entity_manager/ --type pyRepository: hivesolutions/colony-plugins
Length of output: 8644
🏁 Script executed:
#!/bin/bash
# Get the complete get_items_map definition
rg -n -A 50 'def get_items_map' data/src/entity_manager/structures.py | head -70Repository: hivesolutions/colony-plugins
Length of output: 2301
Fix get_fields_for_table method—incorrect assumption about get_items_map() return structure.
The method assumes get_items_map() returns {field_name: definition}, but it actually returns {entity_class: items_dict} (see structures.py line 702). Iterating with .items() at line 156 yields class objects as keys and items dictionaries as values, not field names and definitions. The field filtering logic is broken.
To get only the fields defined directly on this class, extract the items dictionary for the current class and exclude inherited ones:
def get_fields_for_table(self, entity_class):
"""
Returns only the fields defined directly on this class,
not inherited fields.
"""
# Get items map which has structure {cls: items_dict, parent_cls: items_dict, ...}
items_map = entity_class.get_items_map()
# Get fields from all parent classes
parent_fields = set()
for base in entity_class.__bases__:
if hasattr(base, "get_items_map"):
base_items_map = base.get_items_map()
if base in base_items_map:
parent_fields.update(base_items_map[base].keys())
# Return only fields defined on this specific class
this_class_fields = {}
if entity_class in items_map:
all_class_fields = items_map[entity_class]
for name, definition in all_class_fields.items():
if name not in parent_fields:
this_class_fields[name] = definition
return this_class_fields🤖 Prompt for AI Agents
In data/src/entity_manager/inheritance_strategies.py around lines 140 to 160,
the method assumes entity_class.get_items_map() returns a mapping of
field_name->definition but it actually returns a mapping of class->items_dict;
update the logic to treat get_items_map() as {cls: items_dict}: pull items_map =
entity_class.get_items_map(), get the current class's fields from
items_map[entity_class] (if present), collect parent field names by calling
get_items_map() on each base and, if the base is present in its map, add its
keys to parent_fields, then iterate only the current class's items_dict and
exclude any names present in parent_fields before returning this_class_fields.
| def should_create_table(self, entity_class): | ||
| """ | ||
| Only creates a table for the root class in the hierarchy. | ||
| """ | ||
| # Check if this is the root class (defines the strategy) | ||
| if hasattr(entity_class, "__inheritance_strategy__"): | ||
| return True | ||
|
|
||
| # Check if any parent already created the table | ||
| for base in entity_class.__bases__: | ||
| if hasattr(base, "__inheritance_strategy__"): | ||
| return False | ||
|
|
||
| return True |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should_create_table uses hasattr which inherits from parents.
Using hasattr(entity_class, "__inheritance_strategy__") will return True for subclasses even when only the parent defines the attribute. This causes incorrect behavior—subclasses may create tables when they shouldn't.
Use __dict__ to check only the class itself:
def should_create_table(self, entity_class):
"""
Only creates a table for the root class in the hierarchy.
"""
# Check if this is the root class (defines the strategy)
- if hasattr(entity_class, "__inheritance_strategy__"):
+ if "__inheritance_strategy__" in entity_class.__dict__:
return True
# Check if any parent already created the table
for base in entity_class.__bases__:
- if hasattr(base, "__inheritance_strategy__"):
+ if "__inheritance_strategy__" in base.__dict__:
return False
return True📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| def should_create_table(self, entity_class): | |
| """ | |
| Only creates a table for the root class in the hierarchy. | |
| """ | |
| # Check if this is the root class (defines the strategy) | |
| if hasattr(entity_class, "__inheritance_strategy__"): | |
| return True | |
| # Check if any parent already created the table | |
| for base in entity_class.__bases__: | |
| if hasattr(base, "__inheritance_strategy__"): | |
| return False | |
| return True | |
| def should_create_table(self, entity_class): | |
| """ | |
| Only creates a table for the root class in the hierarchy. | |
| """ | |
| # Check if this is the root class (defines the strategy) | |
| if "__inheritance_strategy__" in entity_class.__dict__: | |
| return True | |
| # Check if any parent already created the table | |
| for base in entity_class.__bases__: | |
| if "__inheritance_strategy__" in base.__dict__: | |
| return False | |
| return True |
🤖 Prompt for AI Agents
In data/src/entity_manager/inheritance_strategies.py around lines 203 to 216,
the method should_create_table incorrectly uses hasattr(entity_class,
"__inheritance_strategy__") which returns True for subclasses because it checks
the entire MRO; change the checks to inspect the class's own namespace (use
"__inheritance_strategy__" in entity_class.__dict__) so only the class that
defines the attribute is treated as the root, and likewise when scanning bases
check each base.__dict__ for the attribute to prevent subclasses from falsely
appearing to define the strategy.
| def get_association_table_name(self, cls1, relation_name1, cls2, relation_name2): | ||
| """ | ||
| Uses join_table annotation or falls back to default naming. | ||
| """ | ||
| relation1 = cls1.get_relation(relation_name1) | ||
| join_table = relation1.get("join_table") | ||
|
|
||
| if join_table: | ||
| if isinstance(join_table, dict): | ||
| return join_table.get("name") | ||
| return join_table | ||
|
|
||
| return super(AnnotationBasedStrategy, self).get_association_table_name( | ||
| cls1, relation_name1, cls2, relation_name2 | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential None return when join_table dict lacks name key.
If join_table is a dict without a name key, join_table.get("name") returns None, which may cause issues downstream when constructing SQL. Consider falling back to the superclass behavior in this case.
if join_table:
if isinstance(join_table, dict):
- return join_table.get("name")
+ name = join_table.get("name")
+ if name:
+ return name
+ # Fall back to default if name not specified
+ return super(AnnotationBasedStrategy, self).get_association_table_name(
+ cls1, relation_name1, cls2, relation_name2
+ )
return join_table🤖 Prompt for AI Agents
In data/src/entity_manager/mapping_strategies.py around lines 262 to 276, the
method may return None when join_table is a dict missing the "name" key; modify
the logic to check that join_table.get("name") yields a truthy string and if
not, fall back to calling and returning super(AnnotationBasedStrategy,
self).get_association_table_name(cls1, relation_name1, cls2, relation_name2);
ensure the function always returns a valid table name (string) rather than None.
| if field not in self._filters: | ||
| self._filters[field] = {} | ||
| elif not isinstance(self._filters[field], dict): | ||
| # Convert to dict if it was a direct value | ||
| old_value = self._filters[field] | ||
| self._filters[field] = {"$eq": old_value} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# Search for $eq usage in the codebase
rg -n '\$eq' --type=py -g '!query_builder.py'Repository: hivesolutions/colony-plugins
Length of output: 54
🏁 Script executed:
# Look at the operator_map definition mentioned in the review
rg -n 'operator_map' --type=py -A 10 -B 2Repository: hivesolutions/colony-plugins
Length of output: 1980
🏁 Script executed:
# Search for how EntityManager processes filters
rg -n 'class EntityManager' --type=py -A 30Repository: hivesolutions/colony-plugins
Length of output: 18137
🏁 Script executed:
# Look for test files that use filters to understand expected behavior
fd -e test\.py -o -e _test\.py | head -5Repository: hivesolutions/colony-plugins
Length of output: 188
🏁 Script executed:
# Search for how _filters is used in query_builder.py
rg -n '_filters' data/src/entity_manager/query_builder.py -A 2 -B 2Repository: hivesolutions/colony-plugins
Length of output: 1661
🏁 Script executed:
# Look for find or query methods that use the filters
rg -n 'def find\|def query' data/src/entity_manager/query_builder.py -A 10Repository: hivesolutions/colony-plugins
Length of output: 54
🏁 Script executed:
# Search for how EntityManager handles filter dicts
rg -n 'def find' data/src/entity_manager/system.py -A 15 | head -60Repository: hivesolutions/colony-plugins
Length of output: 975
🏁 Script executed:
# Look at the complete _add_filter method context to understand the logic
sed -n '107,150p' data/src/entity_manager/query_builder.pyRepository: hivesolutions/colony-plugins
Length of output: 1458
🏁 Script executed:
# Search for engine filter handling
rg -n 'filters' data/src/entity_manager/engine*.py | head -40Repository: hivesolutions/colony-plugins
Length of output: 146
🏁 Script executed:
# Look for test cases that combine filters with different operators
rg -n 'filter.*filter' data/src/entity_manager/test.py -B 2 -A 2Repository: hivesolutions/colony-plugins
Length of output: 54
🏁 Script executed:
# Search for any references to $eq in engine or underlying code
find data/src -name "*.py" -type f | xargs grep -l "engine\|Engine" | head -5Repository: hivesolutions/colony-plugins
Length of output: 251
🏁 Script executed:
# Check if there's any handling of $eq in the codebase at all
rg -n '\$eq\|"eq"' --type=pyRepository: hivesolutions/colony-plugins
Length of output: 54
Fix the incorrect $eq operator conversion at line 143.
The code converts a direct value to {"$eq": old_value} when mixing with operator-based filters, but Colony's operator_map explicitly maps "eq": None, indicating direct values should not be wrapped in $eq. The $eq operator is not supported by Colony and doesn't appear anywhere else in the codebase.
When a field already has a direct value and an operator filter is added, the existing value should be preserved as-is without attempting to wrap it in an unsupported $eq operator. This will cause filter combinations to fail silently or produce incorrect results.
🤖 Prompt for AI Agents
In data/src/entity_manager/query_builder.py around lines 138–143, the code
incorrectly wraps an existing direct value into {"$eq": old_value}; change this
so you do NOT create a "$eq" entry. Preserve the existing direct value as-is (do
not wrap it) when mixing with operator-based filters and update the
merging/adding logic to detect a non-dict existing value and handle adding
operators without converting that value into an unsupported "$eq" key (either by
converting the incoming operator into a dict that coexists with the raw value or
by making downstream merge code accept a raw value + operator dict).
| def get(self, **kwargs): | ||
| """ | ||
| Gets a single entity matching the criteria. | ||
| Raises exception if not found or multiple found. | ||
| Usage: | ||
| .get(object_id=123) | ||
| :rtype: EntityClass | ||
| :return: The matching entity | ||
| """ | ||
| self.filter(**kwargs) | ||
| options = self._build_options() | ||
| results = self._entity_manager.find(self._entity_class, options) | ||
|
|
||
| if len(results) == 0: | ||
| raise Exception("No %s found matching criteria" % self._entity_class.__name__) | ||
| elif len(results) > 1: | ||
| raise Exception( | ||
| "Multiple %s found matching criteria" % self._entity_class.__name__ | ||
| ) | ||
|
|
||
| return results[0] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix state mutation in get() method.
Line 330 calls self.filter(**kwargs), which mutates the QueryBuilder's internal state. This makes the query builder unusable for subsequent queries if get() doesn't succeed:
qb = entity_manager.query(Person)
try:
person = qb.get(name="John") # Adds name filter to qb
except Exception:
pass
# Now qb is permanently filtered by name="John"Apply this diff to avoid mutation:
def get(self, **kwargs):
"""
Gets a single entity matching the criteria.
Raises exception if not found or multiple found.
Usage:
.get(object_id=123)
:rtype: EntityClass
:return: The matching entity
"""
- self.filter(**kwargs)
- options = self._build_options()
+ # Clone to avoid mutating self
+ query = self.clone() if not kwargs else self.clone().filter(**kwargs)
+ options = query._build_options()
results = self._entity_manager.find(self._entity_class, options)
if len(results) == 0:
raise Exception("No %s found matching criteria" % self._entity_class.__name__)
elif len(results) > 1:
raise Exception(
"Multiple %s found matching criteria" % self._entity_class.__name__
)
return results[0]🧰 Tools
🪛 Ruff (0.14.6)
335-335: Create your own exception
(TRY002)
337-339: Create your own exception
(TRY002)
🤖 Prompt for AI Agents
In data/src/entity_manager/query_builder.py around lines 319 to 341, the get()
method calls self.filter(**kwargs) which mutates the QueryBuilder instance and
leaks filters to future queries; change get() to create a temporary non-mutating
builder (e.g. clone the current query builder or copy its internal filter state)
and apply the kwargs filter only to that temporary builder, then build options
from the temporary builder and call find on it so self remains unchanged. If a
clone/copy helper exists use it (e.g. tmp = self.clone(); tmp.filter(**kwargs));
otherwise shallow-copy the internal filter/criteria structure into a new
QueryBuilder instance, apply kwargs to that copy, and proceed; do not modify
self.
| # Check inheritance strategy to see if table should be created | ||
| # This allows strategies like SingleTableStrategy to only create | ||
| # a table for the root class in the hierarchy | ||
| strategy = inheritance_strategies.get_inheritance_strategy(entity_class) | ||
| if not strategy.should_create_table(entity_class): | ||
| # Strategy says not to create a table for this class | ||
| # (e.g., in single-table inheritance, only root creates table) | ||
| return |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
find . -name "inheritance_strategies.py" -o -name "system.py" | head -20Repository: hivesolutions/colony-plugins
Length of output: 918
🏁 Script executed:
cd data/src/entity_manager && ls -laRepository: hivesolutions/colony-plugins
Length of output: 1199
🏁 Script executed:
cat -n data/src/entity_manager/inheritance_strategies.py | head -100Repository: hivesolutions/colony-plugins
Length of output: 4069
🏁 Script executed:
cat -n data/src/entity_manager/inheritance_strategies.py | tail -300Repository: hivesolutions/colony-plugins
Length of output: 12016
🏁 Script executed:
wc -l data/src/entity_manager/inheritance_strategies.pyRepository: hivesolutions/colony-plugins
Length of output: 125
🏁 Script executed:
sed -n '1925,1950p' data/src/entity_manager/system.pyRepository: hivesolutions/colony-plugins
Length of output: 1360
🏁 Script executed:
grep -n "def ensure_definition" data/src/entity_manager/system.pyRepository: hivesolutions/colony-plugins
Length of output: 123
🏁 Script executed:
grep -n "def index_fields" data/src/entity_manager/system.pyRepository: hivesolutions/colony-plugins
Length of output: 118
🏁 Script executed:
sed -n '1384,1420p' data/src/entity_manager/system.pyRepository: hivesolutions/colony-plugins
Length of output: 1470
🏁 Script executed:
sed -n '2614,2680p' data/src/entity_manager/system.pyRepository: hivesolutions/colony-plugins
Length of output: 2837
🏁 Script executed:
grep -n "index_fields" data/src/entity_manager/system.pyRepository: hivesolutions/colony-plugins
Length of output: 164
🏁 Script executed:
grep -n "def get_name" data/src/entity_manager/system.pyRepository: hivesolutions/colony-plugins
Length of output: 54
🏁 Script executed:
sed -n '1415,1435p' data/src/entity_manager/system.pyRepository: hivesolutions/colony-plugins
Length of output: 912
🏁 Script executed:
grep -n "def get_name" data/src/entity_manager/structures.py | head -5Repository: hivesolutions/colony-plugins
Length of output: 217
🏁 Script executed:
sed -n '552,600p' data/src/entity_manager/structures.pyRepository: hivesolutions/colony-plugins
Length of output: 1919
🏁 Script executed:
grep -n "get_name" data/src/entity_manager/inheritance_strategies.pyRepository: hivesolutions/colony-plugins
Length of output: 54
🏁 Script executed:
sed -n '1929,1960p' data/src/entity_manager/system.pyRepository: hivesolutions/colony-plugins
Length of output: 1649
SingleTableStrategy.should_create_table() has a critical inheritance detection bug, and ensure_definition() unconditionally indexes tables that may not exist
The inheritance strategy hook is correct in principle. However, two real issues need fixing:
-
SingleTableStrategy.should_create_table() incorrectly identifies subclasses as roots
At
data/src/entity_manager/inheritance_strategies.pylines 203–216,should_create_table()useshasattr()to detect the root class:if hasattr(entity_class, "__inheritance_strategy__"): return True for base in entity_class.__bases__: if hasattr(base, "__inheritance_strategy__"): return False return True
Since class attributes are inherited,
hasattr(subclass, "__inheritance_strategy__")returnsTruewhen the root class defines the attribute. This causesshould_create_table()to returnTruefor every subclass and creates per-class tables even in single-table mode, defeating the intended behavior.Replace with a check on
__dict__to detect where the attribute is actually defined:- if hasattr(entity_class, "__inheritance_strategy__"): - return True - - for base in entity_class.__bases__: - if hasattr(base, "__inheritance_strategy__"): - return False + # Root: strategy explicitly defined on this class + if "__inheritance_strategy__" in entity_class.__dict__: + return True + + # Any ancestor defines the strategy => this is a child, skip table + for base in entity_class.__mro__[1:]: + if "__inheritance_strategy__" in getattr(base, "__dict__", {}): + return False
-
ensure_definition() calls index_fields() even when no table was created
At
data/src/entity_manager/system.pyline 1424,ensure_definition()unconditionally callsindex_fields()aftercreate_definition(). Whencreate_definition()returns early becauseshould_create_table()isFalse(line 1941),index_fields()still executes and callsentity_class.get_name()(line 2630 inindex_fields()). For subclasses in single-table inheritance,get_name()returns the subclass table name (e.g., "dog"), not the root table (e.g., "animal"), causing indexing to fail on a non-existent table.Guard
index_fields()with the sameshould_create_table()check, or ensure the inheritance strategy configuresentity_class.get_name()to return the physical root table name even for non-root classes.
🤖 Prompt for AI Agents
In data/src/entity_manager/system.py around lines 1935–1942 and related
ensure_definition() at ~1424, the code unconditionally returns/continues based
on an incorrect inheritance check and still calls index_fields() even when no
table is created; update data/src/entity_manager/inheritance_strategies.py
(around lines 203–216) to detect the presence of __inheritance_strategy__ by
checking entity_class.__dict__ (so only the class that defines the attribute
counts) instead of hasattr(), and in data/src/entity_manager/system.py
ensure_definition() (around line 1424) only call index_fields() when the
strategy reports should_create_table(entity_class) is True (or otherwise use the
strategy to get the root physical table name before indexing) so subclasses that
do not create tables do not trigger indexing for non-existent tables.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (3)
data/src/entity_manager/query_builder.py (2)
140-145: The$eqoperator conversion is unsupported by Colony.This issue was previously flagged. The code converts a direct value to
{"$eq": old_value}when mixing with operator-based filters, but Colony'soperator_mapmaps"eq": None, indicating direct values should not be wrapped. The$eqoperator doesn't appear anywhere else in the codebase and will cause filter combinations to fail.Consider preserving the direct value or using a different merging strategy:
if colony_op is None: # Direct value (exact match) self._filters[field] = value else: # Operator-based filter if field not in self._filters: self._filters[field] = {} elif not isinstance(self._filters[field], dict): - # Convert to dict if it was a direct value - old_value = self._filters[field] - self._filters[field] = {"$eq": old_value} + # Overwrite direct value with operator dict + # (exact match and operator filters on same field are mutually exclusive) + self._filters[field] = {} self._filters[field][colony_op] = value
319-343:get()mutates internal state, making the QueryBuilder unusable for subsequent queries.This issue was previously flagged. Line 330 calls
self.filter(**kwargs), which permanently adds filters to the QueryBuilder instance. Ifget()fails or is called multiple times, the filters accumulate.Apply this diff to avoid mutation:
def get(self, **kwargs): - self.filter(**kwargs) - options = self._build_options() + # Clone to avoid mutating self + query = self.clone().filter(**kwargs) if kwargs else self.clone() + options = query._build_options() results = self._entity_manager.find(self._entity_class, options)data/src/entity_manager/fields.py (1)
216-227:FloatFieldacceptsmin_value/max_valuebut doesn't validate them.This issue was previously flagged. Unlike
IntegerFieldwhich implements range validation,FloatFieldsilently ignores these parameters, which is inconsistent and will surprise users.Apply this diff to add validation matching
IntegerField:class FloatField(Field): def __init__(self, min_value=None, max_value=None, **kwargs): + self.min_value = min_value + self.max_value = max_value + + # Add validation + def validate_range(value): + if min_value is not None and value < min_value: + return False + if max_value is not None and value > max_value: + return False + return True + + if min_value is not None or max_value is not None: + existing_validator = kwargs.get("validator") + if existing_validator: + kwargs["validator"] = lambda v: existing_validator(v) and validate_range(v) + else: + kwargs["validator"] = validate_range + super(FloatField, self).__init__("float", **kwargs)
🧹 Nitpick comments (3)
data/src/entity_manager/query_builder.py (2)
334-341: Consider using custom exception classes instead of genericException.Using generic
Exceptionmakes it harder for callers to handle specific error cases. Per static analysis hint TRY002, consider defining domain-specific exceptions likeEntityNotFoundErrorandMultipleEntitiesFoundError.# At module level: class EntityNotFoundError(Exception): """Raised when a requested entity is not found.""" pass class MultipleEntitiesFoundError(Exception): """Raised when multiple entities match a unique query.""" passThen use these in
get():if len(results) == 0: - raise Exception( + raise EntityNotFoundError( "No %s found matching criteria" % self._entity_class.__name__ ) elif len(results) > 1: - raise Exception( + raise MultipleEntitiesFoundError( "Multiple %s found matching criteria" % self._entity_class.__name__ )
397-455: Q class placeholder noted -to_filters()does not respectconnector.The
to_filters()method ignores theconnectorattribute (AND/OR), merging child filters withdict.update()which silently overwrites duplicate keys. The docstring correctly notes this is not fully integrated yet.For future implementation, consider returning a structured representation that preserves the logical operators:
def to_filters(self): if not self.children: return self.filters # Return structure that backend can interpret return { f"${self.connector.lower()}": [ child.to_filters() if isinstance(child, Q) else child for child in self.children ] }data/src/entity_manager/examples_new_features.py (1)
339-372: Remove redundant import ofmapping_strategies.Line 344 re-imports
mapping_strategieswhich shadows the module-level import at line 43. While the code works, this is confusing and flagged by static analysis.def example_mapping_strategies(entity_manager): """ Demonstrates how to use different mapping strategies. """ # Configure entity manager with convention-based mapping - from . import mapping_strategies # Option 1: Set globally via entity manager options
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
data/src/entity_manager/examples_new_features.py(1 hunks)data/src/entity_manager/fields.py(1 hunks)data/src/entity_manager/query_builder.py(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
data/src/entity_manager/query_builder.py (3)
data/src/entity_manager/lazy_collections.py (7)
filter(214-232)get(390-395)append(172-180)count(207-212)all(191-197)first(199-205)remove(182-189)rest/src/rest/system.py (1)
field(2233-2247)data/src/entity_manager/system.py (1)
find(2491-2549)
🪛 Ruff (0.14.6)
data/src/entity_manager/query_builder.py
1-1: Shebang is present but file is not executable
(EXE001)
335-337: Create your own exception
(TRY002)
339-341: Create your own exception
(TRY002)
data/src/entity_manager/fields.py
1-1: Shebang is present but file is not executable
(EXE001)
226-226: Unused method argument: min_value
(ARG002)
226-226: Unused method argument: max_value
(ARG002)
data/src/entity_manager/examples_new_features.py
1-1: Shebang is present but file is not executable
(EXE001)
259-259: Local variable old_results is assigned to but never used
Remove assignment to unused variable old_results
(F841)
270-270: Local variable new_results is assigned to but never used
Remove assignment to unused variable new_results
(F841)
280-280: Local variable adults is assigned to but never used
Remove assignment to unused variable adults
(F841)
289-289: Local variable john is assigned to but never used
Remove assignment to unused variable john
(F841)
292-292: Local variable count is assigned to but never used
Remove assignment to unused variable count
(F841)
295-295: Local variable has_adults is assigned to but never used
Remove assignment to unused variable has_adults
(F841)
298-298: Local variable youngest is assigned to but never used
Remove assignment to unused variable youngest
(F841)
301-301: Local variable people_with_dogs is assigned to but never used
Remove assignment to unused variable people_with_dogs
(F841)
304-304: Local variable locked_person is assigned to but never used
Remove assignment to unused variable locked_person
(F841)
344-344: Redefinition of unused mapping_strategies from line 43
Remove definition: mapping_strategies
(F811)
347-347: Local variable entity_manager_with_conventions is assigned to but never used
Remove assignment to unused variable entity_manager_with_conventions
(F841)
363-363: Local variable entity_manager_annotated is assigned to but never used
Remove assignment to unused variable entity_manager_annotated
(F841)
401-401: Local variable all_animals is assigned to but never used
Remove assignment to unused variable all_animals
(F841)
406-406: Local variable all_dogs is assigned to but never used
Remove assignment to unused variable all_dogs
(F841)
🔇 Additional comments (24)
data/src/entity_manager/query_builder.py (7)
1-30: LGTM - License header and module metadata.The license header and module metadata are correctly structured.
32-75: Well-designed fluent query builder class.The
QueryBuilderclass provides a clean, Django-inspired interface for constructing queries. The initialization properly sets up all internal state for filters, ordering, pagination, and eager loading.
147-169: LGTM - Order by implementation.The
order_bymethod correctly handles both ascending and descending ordering with the-prefix convention.
171-199: LGTM - Pagination methods.The
limitandoffsetmethods correctly set the pagination state.
201-243: LGTM - Eager loading, locking, and field projection.The
eager,lock, andonlymethods provide useful query configuration options.
287-297:first()modifiesoptionsbut doesn't affectself- good pattern.Unlike
get(), this method correctly creates a local copy of options without mutating internal state.
376-394: LGTM - Clone method implementation.The
clone()method correctly deep-copies mutable state (_filters,_eager_relations) and creates shallow copies of immutable or simple collections. The import ofcopyinside the method is acceptable for a rarely-called utility.data/src/entity_manager/examples_new_features.py (8)
1-46: Example module provides comprehensive feature documentation.The module header, imports, and structure are well-organized for demonstrating the new Entity Manager features.
54-103: LGTM - ModernPerson and ModernDog entity examples.These examples clearly demonstrate descriptor-based field definitions with validation constraints, relationships, and default values.
110-143: LGTM - Single-table inheritance example.The
Animal,Dog, andCathierarchy demonstrates single-table inheritance with discriminator columns correctly.
150-176: LGTM - Embedded components example.The
Addresscomponent andPersonWithAddressentity demonstrate embedded field flattening with prefixes.
183-246: LGTM - Convention and annotation-based mapping examples.These examples effectively illustrate the different mapping strategy approaches.
254-313: LGTM - Query builder usage examples.The examples comprehensively demonstrate the fluent API including filtering, ordering, pagination, eager loading, locking, and bulk operations. The unused variable warnings from static analysis are expected for example code.
315-336: LGTM - Lazy collections example.The example clearly demonstrates the N+1 problem and its solutions using batch loading and eager loading.
437-488: Integration guide provides helpful implementation roadmap.The integration notes clearly document the changes needed in
system.pyandstructures.pyto fully integrate the new features.data/src/entity_manager/fields.py (9)
32-78: Well-designed base Field descriptor.The
Fieldclass provides a clean descriptor interface with proper validation hooks, nullable constraints, and backward compatibility viato_dict().
80-113: LGTM - Descriptor protocol implementation.The
__set_name__,__get__, and__set__methods correctly implement the descriptor protocol with proper validation.
115-138: LGTM - Legacy format conversion.The
to_dict()method provides good backward compatibility with the existing dict-based field definitions.
140-164: LGTM - IdField implementation.Correctly sets the
idflag and optional generation parameters.
167-181: LGTM - TextField implementation.Simple and correct propagation of
max_lengthto the base class.
183-213: LGTM - IntegerField with range validation.The range validation implementation correctly composes with existing validators and uses closure capture for
min_value/max_value.
230-254: LGTM - DateField and MetadataField implementations.Simple specialized field types correctly delegating to the base class.
256-340: LGTM - EmbeddedField implementation.The
EmbeddedFielddescriptor correctly handles lazy component creation, flattening on set, and column generation for schema purposes. The use ofdir()andisinstancechecks for field discovery is appropriate.
393-417: LGTM - RelationField.to_dict() implementation.The legacy format conversion correctly handles both string and class targets, and includes all relation metadata.
| def __get__(self, instance, owner): | ||
| if instance is None: | ||
| return self | ||
| # Delegate to entity's lazy loading mechanism | ||
| return instance.__getattribute__(self.name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check EntityClass for __getattribute__ and __getattr__ implementations
rg -n '__getattribute__|__getattr__' data/src/entity_manager/structures.py -C 5Repository: hivesolutions/colony-plugins
Length of output: 904
🏁 Script executed:
#!/bin/bash
# Examine the full RelationField.__get__ implementation and surrounding context
cat -n data/src/entity_manager/fields.py | sed -n '370,395p'Repository: hivesolutions/colony-plugins
Length of output: 1024
🏁 Script executed:
#!/bin/bash
# Check if there's any code that calls __set__ or populates __dict__ for RelationField
# Also check the _try_attr method in EntityClass to understand the fallback
rg -n '_try_attr|RelationField' data/src/entity_manager/structures.py -A 3 -B 1 | head -40Repository: hivesolutions/colony-plugins
Length of output: 977
🏁 Script executed:
#!/bin/bash
# Get the full _try_attr method implementation
cat -n data/src/entity_manager/structures.py | sed -n '4941,4980p'Repository: hivesolutions/colony-plugins
Length of output: 2046
🏁 Script executed:
#!/bin/bash
# Verify the descriptor protocol behavior by checking if there are any tests
# or usage examples that show how RelationField is supposed to work
fd -t f "test.*\.py|.*test\.py" | xargs rg -l "RelationField" 2>/dev/null | head -3Repository: hivesolutions/colony-plugins
Length of output: 54
Infinite recursion in RelationField.__get__ causes runtime crash.
The __get__ method calls instance.__getattribute__(self.name), which triggers EntityClass's __getattribute__ override. This delegates to object.__getattribute__, which re-invokes the descriptor protocol, calling RelationField.__get__ again with identical parameters. This creates infinite recursion on first access before __dict__ is populated.
Fix: Either directly access instance.__dict__[self.name] using object.__getattribute__, or ensure lazy-loaded values are stored in __dict__ before returning to avoid re-triggering the descriptor.
🤖 Prompt for AI Agents
In data/src/entity_manager/fields.py around lines 384 to 388,
RelationField.__get__ currently calls instance.__getattribute__(self.name) which
re-enters the entity's __getattribute__ and triggers the descriptor again
causing infinite recursion; change the implementation to obtain the stored/lazy
value without invoking instance.__getattribute__ — either read directly from the
instance dict via object.__getattribute__(instance, "__dict__")[self.name] (or
use object.__getattribute__(instance, self.name) only if that bypasses the
descriptor) or ensure the lazy-loaded value is stored into
instance.__dict__[self.name] before returning so subsequent accesses do not
re-trigger the descriptor.
| def delete(self): | ||
| """ | ||
| Deletes all entities matching the query. | ||
| :rtype: int | ||
| :return: Number of entities deleted | ||
| """ | ||
| entities = self.all() | ||
| for entity in entities: | ||
| self._entity_manager.remove(entity) | ||
| return len(entities) | ||
|
|
||
| def update(self, **kwargs): | ||
| """ | ||
| Updates all entities matching the query. | ||
| Usage: | ||
| .filter(status=1).update(status=2) | ||
| :type kwargs: dict | ||
| :param kwargs: Fields to update | ||
| :rtype: int | ||
| :return: Number of entities updated | ||
| """ | ||
| entities = self.all() | ||
| for entity in entities: | ||
| for key, value in kwargs.items(): | ||
| setattr(entity, key, value) | ||
| self._entity_manager.update(entity) | ||
| return len(entities) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
rg -n 'transaction|begin|commit|rollback' data/src/entity_manager/system.py -C 3Repository: hivesolutions/colony-plugins
Length of output: 10535
🏁 Script executed:
# Find the EntityManager class and explore its structure
fd -t f 'system\.py' data/src/entity_manager/ | head -20Repository: hivesolutions/colony-plugins
Length of output: 105
🏁 Script executed:
# Check the overall structure of entity_manager directory
git ls-files data/src/entity_manager/ | head -30Repository: hivesolutions/colony-plugins
Length of output: 660
🏁 Script executed:
# Search for transaction patterns in the entire entity_manager module
rg -n 'def.*transaction|context.*manager|@.*transaction' data/src/entity_manager/ -A 2Repository: hivesolutions/colony-plugins
Length of output: 1109
🏁 Script executed:
# Look at the query_builder.py file to understand the entity_manager usage
sed -n '1,50p' data/src/entity_manager/query_builder.pyRepository: hivesolutions/colony-plugins
Length of output: 1710
🏁 Script executed:
# Check the entire delete() and update() methods in query_builder.py
sed -n '345,374p' data/src/entity_manager/query_builder.pyRepository: hivesolutions/colony-plugins
Length of output: 929
🏁 Script executed:
# Check if @transaction decorator is used in query_builder.py
rg -n '@transaction|@.*commit|@.*rollback' data/src/entity_manager/query_builder.pyRepository: hivesolutions/colony-plugins
Length of output: 54
🏁 Script executed:
# Look at the transaction decorator implementation
sed -n '32,100p' data/src/entity_manager/decorators.pyRepository: hivesolutions/colony-plugins
Length of output: 2883
Wrap bulk delete() and update() operations in transactions.
Both methods iterate over entities and perform individual operations without transaction wrapping. If an error occurs mid-way, partial changes persist with no rollback. Wrap these operations with self._entity_manager.begin(), commit(), and rollback(), or apply the @transaction() decorator which provides this pattern automatically.
🤖 Prompt for AI Agents
In data/src/entity_manager/query_builder.py around lines 345 to 374, both
delete() and update() iterate entities and call remove()/update() without
transactional protection; wrap each method in a transaction: either call
self._entity_manager.begin() before the loop, commit() after successful
completion, and rollback() in an except block that re-raises the error, or apply
the existing @transaction() decorator to the methods so the
begin/commit/rollback pattern is handled automatically; ensure commit only
happens on success and rollback is triggered on any exception, and still return
the number of affected entities.
This commit implements proper query generation for different inheritance strategies, ensuring that queries are generated differently based on the strategy used (single_table, joined, or table_per_class). Key Changes: 1. Updated _join_query_f to check inheritance strategy before joining parent tables: - Only joins parent tables if strategy.requires_joins() returns True - Single table and table per class strategies skip parent joins - Joined table strategy continues to use INNER JOIN as before 2. Updated _filter_query_f to add discriminator filtering for single_table: - Automatically adds WHERE clause for discriminator column - Filters by discriminator value to ensure only correct subclass rows - Example: WHERE animal_type = 'dog' for Dog class queries 3. Updated _names_query_f to include discriminator column in SELECT: - Includes discriminator column in SELECT for single_table strategy - Allows identification of entity type when loading from database 4. Added comprehensive documentation: - Updated examples_new_features.py with query generation examples - Shows exact SQL queries for each inheritance strategy - Includes guidance on when to use each strategy Query Generation Examples: - Single Table: SELECT * FROM Animal WHERE animal_type = 'dog' - Joined Table: SELECT * FROM Dog INNER JOIN Animal ON Dog.id = Animal.id - Table Per Class: SELECT * FROM Dog (all fields in Dog table) This ensures that the inheritance strategies defined in inheritance_strategies.py are actually used during query generation, not just during table creation.
Added detailed performance comparison and benchmarking tools to help developers choose the right inheritance strategy for their use case. New Files: 1. INHERITANCE_PERFORMANCE.md - Comprehensive performance guide - Detailed analysis of each strategy - Real-world performance numbers - Query execution time comparisons - Storage efficiency analysis - Best practices and tuning tips - Migration guidance 2. benchmark_inheritance.py - Executable benchmark script - Tests all three inheritance strategies - Measures query performance (read operations) - Measures insert performance (write operations) - Measures storage efficiency - Supports SMALL/MEDIUM/LARGE datasets - Provides visual performance comparison Key Findings (10,000 entities): - Single-class queries: Table Per Class wins (3.37ms vs 3.60ms vs 5.98ms) - Polymorphic queries: Joined Table wins (7.16ms vs 10.29ms vs 10.60ms) - Insert performance: Table Per Class wins (22.30ms vs 40.54ms vs 43.49ms) - Storage efficiency: Table Per Class wins (0.34MB vs 0.52MB vs 0.58MB) - Single Table: Best for polymorphic queries with simple hierarchies - Joined Table: Best balance for complex hierarchies - Table Per Class: Best for isolated single-class queries This helps developers make informed decisions about which inheritance strategy to use based on their specific performance requirements.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
♻️ Duplicate comments (1)
data/src/entity_manager/system.py (1)
1384-1425: Guardindex_fields()when inheritance strategy skips table creationWith the new
create_definition()behavior:strategy = inheritance_strategies.get_inheritance_strategy(entity_class) if not strategy.should_create_table(entity_class): returnsubclasses in strategies like single-table inheritance can legitimately skip having their own physical table. However,
ensure_definition()still unconditionally callsindex_fields(entity_class), andindex_fields()usesentity_class.get_name()to build index DDL. Onceshould_create_table()is corrected to returnFalsefor non-root classes, this will attempt to create indexes on tables that do not exist (e.g.,doginstead of the rootanimaltable) – the same issue already raised in the previous review.You should only call
index_fields()for classes that actually own a table under the active inheritance strategy. For example:# creates the entity class definition in the data source # this process may take some time (expensive operation) self.create_definition(entity_class) - # indexes the various fields of the entity class to be - # sets as indexed in the data source (for fast access) - self.index_fields(entity_class) + # indexes the various fields of the entity class only when + # the inheritance strategy creates a concrete table for it + strategy = inheritance_strategies.get_inheritance_strategy(entity_class) + if strategy.should_create_table(entity_class): + # sets the decorated fields as indexed in the data source (for fast access) + self.index_fields(entity_class)Also applies to: 1935-1952
🧹 Nitpick comments (6)
data/src/entity_manager/test_inheritance_queries.py (3)
35-57: Remove unusedEntityManager/ buffer and blindexceptin joined‑table demoInside
test_joined_table_strategy, theEntityManagerinstance andquery_bufferare never used, and the broadexcept Exceptiononly serves to re‑print the same “expected behavior” text. This adds noise and triggers static‑analysis warnings (F841, BLE001) without improving the demo.You can simplify this block to just the explanatory prints:
- # Create a mock entity manager to inspect query generation - # Note: We can't actually run queries without a database connection, - # but we can inspect what queries would be generated - try: - from entity_manager import system - manager = system.EntityManager(None) # No plugin needed for query inspection - - # Generate a find query for Dog - query_buffer = colony.StringBuffer() - query_buffer.write("select ") - - # This would normally call _names_query_f and _join_query_f - # For now, just demonstrate the expected behavior - print("Expected behavior for Joined Table:") - print("- Query should include: INNER JOIN Animal ON Dog.id = Animal.id") - print("- Parent table fields should be joined") - - except Exception as e: - print(f"Note: Cannot generate actual queries without database: {e}") - print("Expected behavior for Joined Table:") - print("- Query should include: INNER JOIN Animal ON Dog.id = Animal.id") - print("- Parent table fields should be joined") + # For now, just demonstrate the expected behavior; once query generation + # is wired, replace this with real calls and assertions. + print("Expected behavior for Joined Table:") + print("- Query should include: INNER JOIN Animal ON Dog.id = Animal.id") + print("- Parent table fields should be joined")This removes dead code and the blind
exceptwhile keeping the explanatory output.
28-34: Ruff’sRUF012on mutable class attributes in demo entity classesStatic analysis flags the dict field definitions like
id = {"type": "integer", "id": True}as mutable class attributes that should be annotated withtyping.ClassVar. Given these are effectively constant field descriptors used bycolony.EntityClass, there are two reasonable options:
Add minimal typing for lint cleanliness (especially if this file is part of the main package, not just examples):
from typing import ClassVar, Dict, Any class Animal(colony.EntityClass): id: ClassVar[Dict[str, Any]] = {"type": "integer", "id": True} name: ClassVar[Dict[str, Any]] = {"type": "text"}(Apply similarly to
Vehicle,Car,Person,Employee.)Or, if you want to keep the example lightweight, consider disabling
RUF012for this file or these specific lines via# noqa: RUF012.Also applies to: 73-80, 100-106
1-2: Shebang on a non‑executable module and Black formatting failureThe file has a shebang (
#!/usr/bin/python) but is not marked executable, which Ruff flags (EXE001). Since this is imported or run viapython …rather than as a standalone executable, the simplest fix is to drop the shebang and let the file be a normal module:-#!/usr/bin/python -# -*- coding: utf-8 -*- - -""" +# -*- coding: utf-8 -*- + +"""Separately, the CI pipeline reports that Black would reformat this file. After applying any code changes, please run:
black data/src/entity_manager/test_inheritance_queries.pyto make sure the formatting matches the project’s Black configuration.
data/src/entity_manager/system.py (3)
47-49: New mapping and fluent query integration look consistentThe new imports,
mapping_strategyinitialization in__init__, and thequery()helper cleanly extendEntityManagerwithout changing existing behavior, and theQueryBuilderwiring matches the rest of the API surface. Ifmapping_strategyis meant to be internal plumbing only, you might consider renaming it to_mapping_strategyor documenting it as public, but that's optional.Also applies to: 382-385, 439-459
4502-4517: Discriminator column selection is added but not yet wired into type resolutionThe extra select of
strategy.get_discriminator_column(entity_class)is a good hook for single-table inheritance, but:
- It ignores
names/minimal, so the discriminator will always be projected even when callers request a narrow field set._unpack_result_e()/_unpack_result_m()still hard-code_classas the discriminator; the additional column is not consumed anywhere yet.If the goal is to support custom discriminator columns (or move away from
_class), consider routing both saving and result-unpacking through the inheritance strategy (e.g., using strategy-provided discriminator column/value) so this extra column is actually used, or delay selecting it until that wiring exists.
4557-4603: Inheritance-aware joins should rely on a strategy-backed physical table nameMaking parent joins conditional on
strategy.requires_joins(entity_class)is a good step, and likewise for joining parent tables of relation targets. However, both the root join andjoin_tables()still unconditionally useentity_class.get_name()/target_class.get_name()as the physical table name:
- For strategies that do not create per-class tables (e.g., single-table),
should_create_table()may returnFalsefor subclasses, whileget_name()still returns the logical subclass name (dog,cat, etc.). That combination will producefrom <child>/join <child>clauses against tables that won’t exist onceshould_create_table()is fixed and only the root table is created.It would be safer to centralize “storage table name” resolution in the inheritance strategy (e.g.,
strategy.get_table_name(entity_class)), and use that consistently in_join_query_fand relation joins (and in save/update/delete paths) instead of callingget_name()directly, or at least guard child joins with the same strategy check used increate_definition()/ensure_definition().Also applies to: 4743-4779
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
data/src/entity_manager/examples_new_features.py(1 hunks)data/src/entity_manager/system.py(9 hunks)data/src/entity_manager/test_inheritance_queries.py(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
data/src/entity_manager/system.py (2)
data/src/entity_manager/query_builder.py (1)
QueryBuilder(32-394)data/src/entity_manager/inheritance_strategies.py (11)
get_inheritance_strategy(343-379)should_create_table(52-61)should_create_table(131-138)should_create_table(203-216)should_create_table(320-327)get_discriminator_column(63-72)get_discriminator_column(218-233)requires_joins(96-105)requires_joins(162-170)requires_joins(273-277)requires_joins(336-340)
data/src/entity_manager/examples_new_features.py (4)
data/src/entity_manager/fields.py (8)
IdField(140-164)TextField(167-180)IntegerField(183-213)FloatField(216-227)DateField(230-240)MetadataField(243-253)RelationField(343-417)EmbeddedField(256-340)data/src/entity_manager/query_builder.py (10)
filter(76-105)order_by(147-169)limit(171-184)all(277-285)get(319-343)count(299-308)exists(310-317)first(287-297)update(357-374)delete(345-355)data/src/entity_manager/lazy_collections.py (7)
filter(214-232)all(191-197)get(390-395)count(207-212)first(199-205)BatchLoader(252-319)load_relation(272-319)data/src/entity_manager/mapping_strategies.py (2)
ConventionOverConfigurationStrategy(152-192)AnnotationBasedStrategy(195-276)
🪛 GitHub Actions: Main Workflow
data/src/entity_manager/test_inheritance_queries.py
[error] 1-1: Black formatting check failed. 1 file would be reformatted by black. Run 'black .' to reformat.
🪛 Ruff (0.14.6)
data/src/entity_manager/test_inheritance_queries.py
1-1: Shebang is present but file is not executable
(EXE001)
28-28: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
29-29: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
33-33: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
40-40: Local variable manager is assigned to but never used
Remove assignment to unused variable manager
(F841)
52-52: Do not catch blind exception: Exception
(BLE001)
73-73: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
74-74: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
79-79: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
100-100: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
101-101: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
105-105: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
106-106: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
data/src/entity_manager/examples_new_features.py
1-1: Shebang is present but file is not executable
(EXE001)
259-259: Local variable old_results is assigned to but never used
Remove assignment to unused variable old_results
(F841)
270-270: Local variable new_results is assigned to but never used
Remove assignment to unused variable new_results
(F841)
280-280: Local variable adults is assigned to but never used
Remove assignment to unused variable adults
(F841)
289-289: Local variable john is assigned to but never used
Remove assignment to unused variable john
(F841)
292-292: Local variable count is assigned to but never used
Remove assignment to unused variable count
(F841)
295-295: Local variable has_adults is assigned to but never used
Remove assignment to unused variable has_adults
(F841)
298-298: Local variable youngest is assigned to but never used
Remove assignment to unused variable youngest
(F841)
301-301: Local variable people_with_dogs is assigned to but never used
Remove assignment to unused variable people_with_dogs
(F841)
304-304: Local variable locked_person is assigned to but never used
Remove assignment to unused variable locked_person
(F841)
344-344: Redefinition of unused mapping_strategies from line 43
Remove definition: mapping_strategies
(F811)
347-347: Local variable entity_manager_with_conventions is assigned to but never used
Remove assignment to unused variable entity_manager_with_conventions
(F841)
363-363: Local variable entity_manager_annotated is assigned to but never used
Remove assignment to unused variable entity_manager_annotated
(F841)
401-401: Local variable all_animals is assigned to but never used
Remove assignment to unused variable all_animals
(F841)
406-406: Local variable all_dogs is assigned to but never used
Remove assignment to unused variable all_dogs
(F841)
| @@ -0,0 +1,607 @@ | |||
| #!/usr/bin/python | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove shebang or make file executable.
The file has a shebang but is not marked as executable. Since this is an examples/documentation module (not a standalone script), consider removing the shebang.
Apply this diff:
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
# Hive Colony FrameworkCommittable suggestion skipped: line range outside the PR's diff.
🧰 Tools
🪛 Ruff (0.14.6)
1-1: Shebang is present but file is not executable
(EXE001)
🤖 Prompt for AI Agents
In data/src/entity_manager/examples_new_features.py at line 1, the file contains
a shebang (#!/usr/bin/python) but the file is not intended as an executable
script; remove the shebang line or alternatively make the file executable if it
truly should be a script. Update the file by deleting the shebang so it becomes
a normal module used for examples/documentation, or mark the file executable
(chmod +x) and ensure it has a proper module-level guard (if __name__ ==
"__main__") if kept as a runnable script.
| Demonstrates how to use different mapping strategies. | ||
| """ | ||
| # Configure entity manager with convention-based mapping | ||
| from . import mapping_strategies |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove redundant import.
mapping_strategies is already imported at the module level (line 43). The local import is unnecessary.
Apply this diff:
- # Configure entity manager with convention-based mapping
- from . import mapping_strategies
-
# Option 1: Set globally via entity manager optionsCommittable suggestion skipped: line range outside the PR's diff.
🧰 Tools
🪛 Ruff (0.14.6)
344-344: Redefinition of unused mapping_strategies from line 43
Remove definition: mapping_strategies
(F811)
🤖 Prompt for AI Agents
In data/src/entity_manager/examples_new_features.py around line 344, remove the
redundant local import "from . import mapping_strategies" since
mapping_strategies is already imported at the module level (line 43); delete
this line to avoid duplicate imports and keep the module-level import only.
| # retrieves the inheritance strategy for the entity class | ||
| # to add discriminator filtering for single table inheritance | ||
| strategy = inheritance_strategies.get_inheritance_strategy(entity_class) | ||
|
|
||
| # for single table inheritance, add a filter for the discriminator column | ||
| # to ensure we only get rows for this specific class type | ||
| discriminator_column = strategy.get_discriminator_column(entity_class) | ||
| if discriminator_column: | ||
| discriminator_value = strategy.get_discriminator_value(entity_class) | ||
|
|
||
| # writes the where clause or the "and" conjunction | ||
| # in case the where clause is already set | ||
| if is_first: | ||
| query_buffer.write(" where ") | ||
| is_first = False | ||
| else: | ||
| query_buffer.write(" and ") | ||
|
|
||
| # writes the discriminator filter condition | ||
| table_name = entity_class.get_name() | ||
| query_buffer.write(table_name) | ||
| query_buffer.write(".") | ||
| query_buffer.write(discriminator_column) | ||
| query_buffer.write(" = ") | ||
| query_buffer.write( | ||
| self.engine._escape_slash_string( | ||
| self.engine._quote_identifier(discriminator_value) | ||
| ) | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# First, locate the file and read the context around the mentioned lines
cd data && wc -l src/entity_manager/system.pyRepository: hivesolutions/colony-plugins
Length of output: 105
🏁 Script executed:
# Read the context around lines 4847-4875
cd data && sed -n '4820,4900p' src/entity_manager/system.pyRepository: hivesolutions/colony-plugins
Length of output: 3514
🏁 Script executed:
# Search for _get_sql_value method
cd data && rg -n "_get_sql_value" --type pyRepository: hivesolutions/colony-plugins
Length of output: 1533
🏁 Script executed:
# Search for _process_table_name method
cd data && rg -n "_process_table_name" --type pyRepository: hivesolutions/colony-plugins
Length of output: 1216
🏁 Script executed:
# Search for inheritance_strategies and get_discriminator methods
cd data && rg -n "get_discriminator_value|get_discriminator_column" --type pyRepository: hivesolutions/colony-plugins
Length of output: 1164
🏁 Script executed:
# Search for other filter constructions to see the pattern used
cd data && rg -n "_escape_slash_string|_quote_identifier" --type py -B 2 -A 2Repository: hivesolutions/colony-plugins
Length of output: 503
🏁 Script executed:
# Read the _get_sql_value method implementation
cd data && sed -n '4165,4200p' src/entity_manager/structures.pyRepository: hivesolutions/colony-plugins
Length of output: 1600
🏁 Script executed:
# Read the _process_table_name method implementation
cd data && sed -n '5048,5080p' src/entity_manager/system.pyRepository: hivesolutions/colony-plugins
Length of output: 1578
🏁 Script executed:
# Read the get_discriminator_value implementation
cd data && sed -n '235,265p' src/entity_manager/inheritance_strategies.pyRepository: hivesolutions/colony-plugins
Length of output: 1250
🏁 Script executed:
# Check the get_discriminator_column implementation
cd data && sed -n '218,240p' src/entity_manager/inheritance_strategies.pyRepository: hivesolutions/colony-plugins
Length of output: 955
🏁 Script executed:
# Look at an example of how _get_sql_value is used in filters
cd data && sed -n '5150,5170p' src/entity_manager/system.pyRepository: hivesolutions/colony-plugins
Length of output: 917
🏁 Script executed:
# Check full inheritance_strategies.py to understand the strategy pattern
cd data && wc -l src/entity_manager/inheritance_strategies.pyRepository: hivesolutions/colony-plugins
Length of output: 120
🏁 Script executed:
# Read the entire inheritance_strategies.py file to understand discriminator value handling
cd data && cat -n src/entity_manager/inheritance_strategies.pyRepository: hivesolutions/colony-plugins
Length of output: 15242
🏁 Script executed:
# Check where discriminator_column is used at line 4505
cd data && sed -n '4500,4530p' src/entity_manager/system.pyRepository: hivesolutions/colony-plugins
Length of output: 1640
🏁 Script executed:
# Read more of _process_table_name to understand how it handles None table_name
cd data && sed -n '5048,5120p' src/entity_manager/system.pyRepository: hivesolutions/colony-plugins
Length of output: 3307
🏁 Script executed:
# Check if discriminator columns are typically in names_map
cd data && grep -n "names_map\|get_names_map" src/entity_manager/system.py | head -20Repository: hivesolutions/colony-plugins
Length of output: 593
🏁 Script executed:
# Look at other places where format strings are used with %s
cd data && grep -n "query_buffer.write.*%s" src/entity_manager/system.py | head -10Repository: hivesolutions/colony-plugins
Length of output: 203
🏁 Script executed:
# Check if there are any cases where get_discriminator_value can return None
cd data && grep -B 5 -A 5 "get_discriminator_value" src/entity_manager/*.py | grep -i "none\|null"Repository: hivesolutions/colony-plugins
Length of output: 310
🏁 Script executed:
# Check how the current code at line 4505 handles discriminator differently
cd data && sed -n '4495,4535p' src/entity_manager/system.pyRepository: hivesolutions/colony-plugins
Length of output: 2127
🏁 Script executed:
# Check what methods entity_class has available
cd data && grep -n "def get_name\|def get_id" src/entity_manager/structures.py | head -20Repository: hivesolutions/colony-plugins
Length of output: 278
Discriminator WHERE clause should use consistent value-quoting pattern
The current implementation uses direct calls to engine-private helpers:
query_buffer.write(
self.engine._escape_slash_string(
self.engine._quote_identifier(discriminator_value)
)
)This bypasses the type-aware quoting and validation pipeline (entity_class._get_sql_value) that all other filters use throughout the codebase (e.g., lines 5159, 5210, 5268, 5330, 5519, 5570, 5619, 5670). For consistency and maintainability, use _get_sql_value instead:
discriminator_value = strategy.get_discriminator_value(entity_class)
if discriminator_column and discriminator_value is not None:
# ... write where/and clause ...
sql_value = entity_class._get_sql_value(discriminator_column, discriminator_value)
query_buffer.write(table_name + "." + discriminator_column + " = " + sql_value)The None check is warranted for robustness: while SingleTableStrategy.get_discriminator_value() always returns a string, the base InheritanceStrategy contract allows None, so guarding against it prevents silent bugs if an inheritance strategy doesn't override the method.
🤖 Prompt for AI Agents
In data/src/entity_manager/system.py around lines 4847 to 4875, the
discriminator WHERE clause is currently written using engine-private helpers
(_escape_slash_string/_quote_identifier) which bypasses the type-aware
validation/quoting used elsewhere; replace that logic to use
entity_class._get_sql_value for the discriminator value and guard against None:
call strategy.get_discriminator_value(entity_class), if discriminator_column and
discriminator_value is not None then compute sql_value =
entity_class._get_sql_value(discriminator_column, discriminator_value) and write
table_name + "." + discriminator_column + " = " + sql_value into the
query_buffer (preserving the existing where/and is_first handling).
| def test_joined_table_strategy(): | ||
| """ | ||
| Test query generation for joined table inheritance. | ||
| Should generate queries with INNER JOIN for parent tables. | ||
| """ | ||
| print("\n=== Testing Joined Table Strategy ===") | ||
|
|
||
| # Create mock entity classes for joined table inheritance | ||
| class Animal(colony.EntityClass): | ||
| """Base animal class using joined table inheritance (default)""" | ||
| id = {"type": "integer", "id": True} | ||
| name = {"type": "text"} | ||
|
|
||
| class Dog(Animal): | ||
| """Dog subclass - should join with Animal table""" | ||
| breed = {"type": "text"} | ||
|
|
||
| # Create a mock entity manager to inspect query generation | ||
| # Note: We can't actually run queries without a database connection, | ||
| # but we can inspect what queries would be generated | ||
| try: | ||
| from entity_manager import system | ||
| manager = system.EntityManager(None) # No plugin needed for query inspection | ||
|
|
||
| # Generate a find query for Dog | ||
| query_buffer = colony.StringBuffer() | ||
| query_buffer.write("select ") | ||
|
|
||
| # This would normally call _names_query_f and _join_query_f | ||
| # For now, just demonstrate the expected behavior | ||
| print("Expected behavior for Joined Table:") | ||
| print("- Query should include: INNER JOIN Animal ON Dog.id = Animal.id") | ||
| print("- Parent table fields should be joined") | ||
|
|
||
| except Exception as e: | ||
| print(f"Note: Cannot generate actual queries without database: {e}") | ||
| print("Expected behavior for Joined Table:") | ||
| print("- Query should include: INNER JOIN Animal ON Dog.id = Animal.id") | ||
| print("- Parent table fields should be joined") | ||
|
|
||
|
|
||
| def test_single_table_strategy(): | ||
| """ | ||
| Test query generation for single table inheritance. | ||
| Should NOT generate joins, but should add discriminator filter. | ||
| """ | ||
| print("\n=== Testing Single Table Strategy ===") | ||
|
|
||
| # Create mock entity classes for single table inheritance | ||
| class Vehicle(colony.EntityClass): | ||
| """Base vehicle class using single table inheritance""" | ||
| __inheritance_strategy__ = "single_table" | ||
| __discriminator_column__ = "vehicle_type" | ||
| __discriminator_value__ = "vehicle" | ||
|
|
||
| id = {"type": "integer", "id": True} | ||
| name = {"type": "text"} | ||
|
|
||
| class Car(Vehicle): | ||
| """Car subclass - should NOT join, but filter by discriminator""" | ||
| __discriminator_value__ = "car" | ||
| num_doors = {"type": "integer"} | ||
|
|
||
| print("Expected behavior for Single Table:") | ||
| print("- Query should NOT include any JOIN clauses for parent tables") | ||
| print("- Query should include: WHERE vehicle_type = 'car'") | ||
| print("- All fields (from Vehicle and Car) are in the same table") | ||
| print("- SELECT should include the discriminator column: vehicle_type") | ||
|
|
||
|
|
||
| def test_table_per_class_strategy(): | ||
| """ | ||
| Test query generation for table per class inheritance. | ||
| Should NOT generate joins to parent tables. | ||
| """ | ||
| print("\n=== Testing Table Per Class Strategy ===") | ||
|
|
||
| # Create mock entity classes for table per class inheritance | ||
| class Person(colony.EntityClass): | ||
| """Base person class using table per class inheritance""" | ||
| __inheritance_strategy__ = "table_per_class" | ||
|
|
||
| id = {"type": "integer", "id": True} | ||
| name = {"type": "text"} | ||
|
|
||
| class Employee(Person): | ||
| """Employee subclass - should have its own complete table""" | ||
| employee_id = {"type": "text"} | ||
| department = {"type": "text"} | ||
|
|
||
| print("Expected behavior for Table Per Class:") | ||
| print("- Query should NOT include any JOIN clauses for parent tables") | ||
| print("- Employee table contains ALL fields (id, name, employee_id, department)") | ||
| print("- Query is simply: SELECT * FROM Employee") | ||
|
|
||
|
|
||
| def demonstrate_query_differences(): | ||
| """ | ||
| Main function to demonstrate the differences in query generation | ||
| between the three inheritance strategies. | ||
| """ | ||
| print("=" * 70) | ||
| print("INHERITANCE STRATEGY QUERY GENERATION DEMONSTRATION") | ||
| print("=" * 70) | ||
| print("\nThis demonstrates how different inheritance strategies should") | ||
| print("generate different SQL queries:") | ||
|
|
||
| test_joined_table_strategy() | ||
| test_single_table_strategy() | ||
| test_table_per_class_strategy() | ||
|
|
||
| print("\n" + "=" * 70) | ||
| print("SUMMARY OF DIFFERENCES") | ||
| print("=" * 70) | ||
| print("\n1. Joined Table (default):") | ||
| print(" - Creates separate tables for each class") | ||
| print(" - Uses INNER JOINs to combine parent and child data") | ||
| print(" - Each table only has its own fields") | ||
|
|
||
| print("\n2. Single Table:") | ||
| print(" - Single table for entire hierarchy") | ||
| print(" - NO joins required") | ||
| print(" - Uses discriminator column to filter by type") | ||
| print(" - WHERE clause filters on discriminator value") | ||
|
|
||
| print("\n3. Table Per Class:") | ||
| print(" - Each concrete class has complete table with all fields") | ||
| print(" - NO joins required") | ||
| print(" - Each table is self-contained") | ||
| print("\n" + "=" * 70) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Print‑only “tests” don’t exercise the new inheritance strategies
All three test_*_strategy functions only define ad‑hoc entity classes and print “expected behavior”; they never use the real inheritance strategy implementation or query builder, and there are no assertions. As a result, this file doesn’t actually validate the new inheritance mapping behavior introduced in the PR.
Consider either:
- Converting this into real automated tests that:
- Use the actual
InheritanceStrategy/get_inheritance_strategy/ query‑builder APIs, and - Assert on the generated SQL (or structured query representation), or
- Use the actual
- Moving it to an
examples/ordocs/script (renaming functions away from thetest_prefix) if the goal is purely explanatory, and adding separate assertion‑based tests for coverage.
🧰 Tools
🪛 Ruff (0.14.6)
28-28: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
29-29: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
33-33: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
40-40: Local variable manager is assigned to but never used
Remove assignment to unused variable manager
(F841)
52-52: Do not catch blind exception: Exception
(BLE001)
73-73: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
74-74: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
79-79: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
100-100: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
101-101: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
105-105: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
106-106: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
🤖 Prompt for AI Agents
In data/src/entity_manager/test_inheritance_queries.py lines 18-147 the
functions are print-only “tests” that only define ad-hoc classes and print
expected behavior without invoking the real inheritance strategy or asserting
generated SQL; convert these into real automated tests or move them to examples.
Replace each print-based test with a pytest function that imports the real APIs
(e.g., system.EntityManager, InheritanceStrategy/get_inheritance_strategy or the
project’s query-builder), register or build the Entity classes through the
manager, call the actual query-generation method for the subclass, and assert
the generated SQL or structured query: for joined-table assert INNER JOIN with
parent ON child.id = parent.id and parent fields present; for single-table
assert no JOINs, discriminator column selected and WHERE discriminator = value;
for table-per-class assert no JOINs and SELECT from subclass table contains all
fields; alternatively, if the file is meant as documentation, move it to
examples/ and rename functions to non-test names and add a short unit test file
that covers the three assertion-based checks using fixtures/mocks for the
EntityManager.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.
Added comprehensive migration guide and script to help users migrate from joined table inheritance to table per class inheritance strategy. Features: - Progressive migration with batch processing - Progress tracking and resumability - Data validation - Detailed logging - CLI and programmatic interfaces The migration provides significant performance improvements (~8x faster queries) for large databases with inheritance hierarchies.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
♻️ Duplicate comments (1)
data/src/entity_manager/test_inheritance_queries.py (1)
18-59: Print-only demonstrations don't provide test coverage.This concern was previously raised. The functions print expected behaviors but don't exercise the actual inheritance strategy implementation or make assertions.
🧹 Nitpick comments (15)
data/src/entity_manager/migrate_inheritance.py (5)
41-52: File read without explicit encoding may cause issues on some platforms.While
json.load()handles encoding, explicitly specifying encoding is a best practice for cross-platform compatibility.def _load_progress(self) -> dict: """Load progress from file if it exists.""" if os.path.exists(self.progress_file): - with open(self.progress_file, 'r') as f: + with open(self.progress_file, 'r', encoding='utf-8') as f: return json.load(f)
54-58: File write should specify encoding explicitly.def save(self): """Save current progress to file.""" self.data['last_update'] = datetime.now().isoformat() - with open(self.progress_file, 'w') as f: + with open(self.progress_file, 'w', encoding='utf-8') as f: json.dump(self.data, f, indent=2)
210-218: Uselogging.exceptionfor better stack trace capture.When logging errors in exception handlers,
logging.exceptionautomatically includes the stack trace, providing better debugging information.try: # Count records in source count = self.source_manager.count(entity_class, {}) return count except Exception as e: - self.logger.error(f"Error counting {entity_class.__name__}: {e}") + self.logger.exception(f"Error counting {entity_class.__name__}: {e}") return 0
251-259: Uselogging.exceptionfor error logging in exception handler.try: # Save to target database self.target_manager.save(record) migrated_count += 1 except Exception as e: error_msg = f"Error migrating {entity_class.__name__} record {getattr(record, 'object_id', 'unknown')}: {e}" - self.logger.error(error_msg) + self.logger.exception(error_msg) self.statistics['errors'].append(error_msg)
445-446: File read should specify encoding explicitly.- with open(config_file, 'r') as f: + with open(config_file, 'r', encoding='utf-8') as f: config = json.load(f)data/src/entity_manager/INHERITANCE_PERFORMANCE.md (4)
72-84: Add language specifier to fenced code block.The code block starting at line 74 is missing a language specifier, which affects syntax highlighting and lint compliance.
### Performance Numbers (10,000 Animals: 5k Dogs, 3k Cats, 2k Birds) -``` +```text Operation | Time -----------------------------|---------- Find all Dogs | 2.1 ms ⚡⚡⚡
163-176: Add language specifier to fenced code block.### Performance Numbers (10,000 Animals: 5k Dogs, 3k Cats, 2k Birds) -``` +```text Operation | Time -----------------------------|---------- Find all Dogs | 18.3 ms ❌
257-270: Add language specifier to fenced code block.### Performance Numbers (10,000 Animals: 5k Dogs, 3k Cats, 2k Birds) -``` +```text Operation | Time -----------------------------|---------- Find all Dogs | 2.3 ms ⚡⚡⚡
282-298: Add language specifier to fenced code blocks in comparison section.Multiple code blocks in the head-to-head comparison section are missing language specifiers (lines 285, 293, 301, 315, 323, 331).
### Scenario 1: Find 100 Dogs by breed -``` +```text Single Table: 0.5 ms ⚡⚡⚡ WINNER Joined Table: 4.2 ms ❌ Table Per Class: 0.6 ms ⚡⚡⚡ ```data/src/entity_manager/MIGRATION_GUIDE.md (2)
94-103: Add language specifier to log output code block.The code block showing example output should have a language specifier for consistency.
**Output example:** -``` +```text 2025-11-30 10:00:00 - InheritanceMigrator - INFO - Connected to source: sqlite:///production.db 2025-11-30 10:00:01 - InheritanceMigrator - INFO - Creating target database schema...
273-278: Add language specifier to log filename code block.Each migration creates a timestamped log file: -``` +```text migration_20251130_100000.log ```data/src/entity_manager/test_inheritance_queries.py (2)
40-56: Unused variable and overly broad exception handling.The
managervariable is assigned but never used. Additionally, catching bareExceptionis too broad.try: from entity_manager import system - manager = system.EntityManager(None) # No plugin needed for query inspection - - # Generate a find query for Dog - query_buffer = colony.StringBuffer() - query_buffer.write("select ") - - # This would normally call _names_query_f and _join_query_f - # For now, just demonstrate the expected behavior + # Demonstrate expected behavior - actual query generation + # requires database connection print("Expected behavior for Joined Table:") print("- Query should include: INNER JOIN Animal ON Dog.id = Animal.id") print("- Parent table fields should be joined") - except Exception as e: + except ImportError as e: print(f"Note: Cannot generate actual queries without database: {e}") print("Expected behavior for Joined Table:") print("- Query should include: INNER JOIN Animal ON Dog.id = Animal.id") print("- Parent table fields should be joined")
157-158: Consider renaming file or functions to clarify this is a demonstration, not a test suite.The
test_prefix and filename suggest these are automated tests, but they're actually demonstrations. Consider renaming todemo_inheritance_queries.pywith functions likedemonstrate_joined_table_strategy()to avoid confusion with test discovery tools.data/src/entity_manager/benchmark_inheritance.py (2)
313-313: Remove unused function parameternum_dogs.The
num_dogsparameter is unused; the actual dog count is retrieved from the query result.-def benchmark_queries(conn, strategy_name, num_dogs): +def benchmark_queries(conn, strategy_name): """Run benchmark queries and return results."""Update call sites accordingly:
- result = benchmark_queries(conn, "Single Table", num_dogs) + result = benchmark_queries(conn, "Single Table")
330-331: Unused variables from fetch operations.The
rowsandrowvariables fromfetchall()andfetchone()calls are assigned but never used. This is fine for timing purposes, but consider using_to indicate intentional discard.- rows = cursor.fetchall() + _ = cursor.fetchall() # Consume results for timing result.add_timing("Find all dogs", (time.time() - start) * 1000)Also applies to: 346-347, 362-363, 388-389
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
data/src/entity_manager/INHERITANCE_PERFORMANCE.md(1 hunks)data/src/entity_manager/MIGRATION_GUIDE.md(1 hunks)data/src/entity_manager/benchmark_inheritance.py(1 hunks)data/src/entity_manager/migrate_inheritance.py(1 hunks)data/src/entity_manager/test_inheritance_queries.py(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
data/src/entity_manager/migrate_inheritance.py (1)
data/src/entity_manager/system.py (2)
create_definition(1929-1951)find(2491-2549)
data/src/entity_manager/benchmark_inheritance.py (1)
data/src/entity_manager/lazy_collections.py (1)
append(172-180)
data/src/entity_manager/test_inheritance_queries.py (1)
data/src/entity_manager/examples_new_features.py (2)
Animal(110-124)Dog(127-133)
🪛 ast-grep (0.40.0)
data/src/entity_manager/benchmark_inheritance.py
[warning] 434-434: The function mktemp is deprecated. When using this function, it is possible for an attacker to modify the created file before the filename is returned. Use NamedTemporaryFile() instead and pass it the delete=False parameter.
Context: tempfile.mktemp(suffix='.db')
Note: [CWE-377]: Insecure Temporary File [OWASP A01:2021]: Broken Access Control [REFERENCES]
https://docs.python.org/3/library/tempfile.html#tempfile.mktemp
https://owasp.org/Top10/A01_2021-Broken_Access_Control
(avoid-mktemp-python)
[warning] 447-447: The function mktemp is deprecated. When using this function, it is possible for an attacker to modify the created file before the filename is returned. Use NamedTemporaryFile() instead and pass it the delete=False parameter.
Context: tempfile.mktemp(suffix='.db')
Note: [CWE-377]: Insecure Temporary File [OWASP A01:2021]: Broken Access Control [REFERENCES]
https://docs.python.org/3/library/tempfile.html#tempfile.mktemp
https://owasp.org/Top10/A01_2021-Broken_Access_Control
(avoid-mktemp-python)
[warning] 460-460: The function mktemp is deprecated. When using this function, it is possible for an attacker to modify the created file before the filename is returned. Use NamedTemporaryFile() instead and pass it the delete=False parameter.
Context: tempfile.mktemp(suffix='.db')
Note: [CWE-377]: Insecure Temporary File [OWASP A01:2021]: Broken Access Control [REFERENCES]
https://docs.python.org/3/library/tempfile.html#tempfile.mktemp
https://owasp.org/Top10/A01_2021-Broken_Access_Control
(avoid-mktemp-python)
🪛 GitHub Actions: Main Workflow
data/src/entity_manager/migrate_inheritance.py
[error] 1-1: Black would reformat this file. Run 'black' to format.
data/src/entity_manager/benchmark_inheritance.py
[error] 1-1: Black would reformat this file. Run 'black' to format.
🪛 LanguageTool
data/src/entity_manager/MIGRATION_GUIDE.md
[style] ~391-~391: As an alternative to the over-used intensifier ‘very’, consider replacing this phrase.
Context: ....
Migrating Incrementally
For very large databases, you can migrate entity by en...
(EN_WEAK_ADJECTIVE)
data/src/entity_manager/INHERITANCE_PERFORMANCE.md
[style] ~91-~91: As an alternative to the over-used intensifier ‘very’, consider replacing this phrase.
Context: ... Many subclass-specific fields (creates very wide tables)
2. Joined Table In...
(EN_WEAK_ADJECTIVE)
[style] ~137-~137: As an alternative to the over-used intensifier ‘very’, consider replacing this phrase.
Context: ...egradation)
- Polymorphic queries: Very expensive (UNION of all subclass tables)
...
(EN_WEAK_ADJECTIVE)
[style] ~399-~399: As an alternative to the over-used intensifier ‘very’, consider replacing this phrase.
Context: ...Consider partitioning**: If tables grow very large
Migration Between Strategi...
(EN_WEAK_ADJECTIVE)
🪛 markdownlint-cli2 (0.18.1)
data/src/entity_manager/MIGRATION_GUIDE.md
95-95: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
276-276: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
data/src/entity_manager/INHERITANCE_PERFORMANCE.md
95-95: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
276-276: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🪛 Ruff (0.14.6)
data/src/entity_manager/migrate_inheritance.py
1-1: Shebang is present but file is not executable
(EXE001)
215-215: Consider moving this statement to an else block
(TRY300)
216-216: Do not catch blind exception: Exception
(BLE001)
217-217: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
256-256: Do not catch blind exception: Exception
(BLE001)
258-258: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
403-403: Consider moving this statement to an else block
(TRY300)
500-500: f-string without any placeholders
Remove extraneous f prefix
(F541)
data/src/entity_manager/benchmark_inheritance.py
1-1: Shebang is present but file is not executable
(EXE001)
313-313: Unused function argument: num_dogs
(ARG001)
362-362: Local variable row is assigned to but never used
Remove assignment to unused variable row
(F841)
388-388: Local variable rows is assigned to but never used
Remove assignment to unused variable rows
(F841)
435-435: Use of insecure and deprecated function (mktemp)
(S306)
448-448: Use of insecure and deprecated function (mktemp)
(S306)
461-461: Use of insecure and deprecated function (mktemp)
(S306)
data/src/entity_manager/test_inheritance_queries.py
1-1: Shebang is present but file is not executable
(EXE001)
29-29: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
30-30: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
35-35: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
43-43: Local variable manager is assigned to but never used
Remove assignment to unused variable manager
(F841)
55-55: Do not catch blind exception: Exception
(BLE001)
77-77: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
78-78: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
84-84: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
106-106: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
107-107: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
112-112: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
113-113: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
🔇 Additional comments (4)
data/src/entity_manager/INHERITANCE_PERFORMANCE.md (1)
1-460: LGTM! Comprehensive and well-structured performance documentation.The documentation provides valuable insights for developers choosing between inheritance strategies, with clear benchmarks, use-case guidance, and tuning tips.
data/src/entity_manager/MIGRATION_GUIDE.md (1)
1-531: LGTM! Excellent migration guide with comprehensive coverage.The documentation is thorough, covering the complete migration workflow from preparation to post-migration monitoring. The troubleshooting section and programmatic usage examples are particularly helpful.
data/src/entity_manager/benchmark_inheritance.py (2)
29-64: LGTM! Well-structured benchmark result container.The
BenchmarkResultclass provides clear output formatting with intuitive speed indicators.
66-194: LGTM! Schema creation functions accurately represent each inheritance strategy.The three schema creation functions correctly model the differences between single-table, joined-table, and table-per-class inheritance patterns.
| @@ -0,0 +1,511 @@ | |||
| #!/usr/bin/python | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pipeline failure: Black formatting required.
The CI pipeline indicates this file needs reformatting with Black. Run black on this file to fix formatting issues.
🧰 Tools
🪛 GitHub Actions: Main Workflow
[error] 1-1: Black would reformat this file. Run 'black' to format.
🪛 Ruff (0.14.6)
1-1: Shebang is present but file is not executable
(EXE001)
🤖 Prompt for AI Agents
In data/src/entity_manager/benchmark_inheritance.py at line 1 the file fails CI
due to Black formatting; run the Black formatter on this file (e.g., execute
`black data/src/entity_manager/benchmark_inheritance.py`) or run `black .` from
the repo root to reformat all files, then commit the updated file so the
pipeline passes.
| db_path = tempfile.mktemp(suffix='.db') | ||
| conn = sqlite3.connect(db_path) | ||
| create_single_table_schema(conn) | ||
| insert_time = populate_single_table(conn, num_dogs, num_cats, num_birds) | ||
| result = benchmark_queries(conn, "Single Table", num_dogs) | ||
| result.add_timing("Insert all entities", insert_time) | ||
| result.storage_mb = get_db_size(db_path) | ||
| conn.close() | ||
| os.unlink(db_path) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace deprecated tempfile.mktemp with secure alternative.
tempfile.mktemp is deprecated and has a race condition vulnerability (CWE-377) where an attacker could modify the file before it's used. Use tempfile.NamedTemporaryFile with delete=False instead.
+import contextlib
+
+@contextlib.contextmanager
+def temp_db():
+ """Create a temporary database file safely."""
+ with tempfile.NamedTemporaryFile(suffix='.db', delete=False) as f:
+ db_path = f.name
+ try:
+ yield db_path
+ finally:
+ if os.path.exists(db_path):
+ os.unlink(db_path)
def run_benchmark(size='MEDIUM'):
# ...
# Benchmark 1: Single Table
print("Benchmarking Single Table Strategy...")
- db_path = tempfile.mktemp(suffix='.db')
- conn = sqlite3.connect(db_path)
- create_single_table_schema(conn)
- insert_time = populate_single_table(conn, num_dogs, num_cats, num_birds)
- result = benchmark_queries(conn, "Single Table", num_dogs)
- result.add_timing("Insert all entities", insert_time)
- result.storage_mb = get_db_size(db_path)
- conn.close()
- os.unlink(db_path)
- results.append(result)
+ with temp_db() as db_path:
+ conn = sqlite3.connect(db_path)
+ create_single_table_schema(conn)
+ insert_time = populate_single_table(conn, num_dogs, num_cats, num_birds)
+ result = benchmark_queries(conn, "Single Table", num_dogs)
+ result.add_timing("Insert all entities", insert_time)
+ result.storage_mb = get_db_size(db_path)
+ conn.close()
+ results.append(result)Also applies to: 448-456, 461-469
🧰 Tools
🪛 Ruff (0.14.6)
435-435: Use of insecure and deprecated function (mktemp)
(S306)
🤖 Prompt for AI Agents
In data/src/entity_manager/benchmark_inheritance.py around lines 435-443 (and
similarly at 448-456, 461-469), replace the deprecated and unsafe use of
tempfile.mktemp with tempfile.NamedTemporaryFile(delete=False) to securely
create a temp file path: create a NamedTemporaryFile, capture its .name for
sqlite3.connect, close the NamedTemporaryFile before opening the DB (important
on Windows), use try/finally to ensure the connection is closed and
os.unlink(file.name) is called in the finally block to remove the file, and set
file.delete=False so you can control removal explicitly.
| @@ -0,0 +1,515 @@ | |||
| #!/usr/bin/env python | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pipeline failure: Black formatting required.
The CI pipeline indicates this file needs reformatting with Black. Run black on this file to fix formatting issues.
🧰 Tools
🪛 GitHub Actions: Main Workflow
[error] 1-1: Black would reformat this file. Run 'black' to format.
🪛 Ruff (0.14.6)
1-1: Shebang is present but file is not executable
(EXE001)
🤖 Prompt for AI Agents
In data/src/entity_manager/migrate_inheritance.py around lines 1 to 1 the file
fails CI because it needs Black formatting; run the Black formatter against this
file (e.g., black data/src/entity_manager/migrate_inheritance.py), review any
changed formatting, stage and commit the reformatted file, and push the commit
so the pipeline passes.
| # Count in both databases | ||
| source_count = self._get_total_count(entity_class) | ||
|
|
||
| # For target, we need to temporarily set it up to read from target | ||
| # This is a simplified check - in production you'd want more thorough validation | ||
| target_count = self.progress.get_migrated_count(entity_name) | ||
|
|
||
| if source_count != target_count: | ||
| self.logger.error( | ||
| f"Validation failed for {entity_name}: " | ||
| f"source={source_count}, target={target_count}" | ||
| ) | ||
| all_valid = False | ||
| else: | ||
| self.logger.info(f"Validation passed for {entity_name}: {target_count} records") | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Validation compares source count against progress file, not actual target database.
The _validate_migration method compares source_count against self.progress.get_migrated_count() which reads from the progress file, not the actual target database. This doesn't verify data was actually persisted correctly.
Consider querying the target database directly for a more robust validation:
def _validate_migration(self) -> bool:
"""
Validate that migration was successful.
Returns:
True if validation passed, False otherwise
"""
self.logger.info("Validating migration...")
all_valid = True
for entity_class in self.entity_classes:
entity_name = entity_class.__name__
# Count in both databases
source_count = self._get_total_count(entity_class)
- # For target, we need to temporarily set it up to read from target
- # This is a simplified check - in production you'd want more thorough validation
- target_count = self.progress.get_migrated_count(entity_name)
+ # Count in target database
+ try:
+ target_count = self.target_manager.count(entity_class, {})
+ except Exception as e:
+ self.logger.exception(f"Error counting {entity_name} in target: {e}")
+ target_count = -1
+ all_valid = False
+ continue
if source_count != target_count:Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In data/src/entity_manager/migrate_inheritance.py around lines 341 to 356, the
validation currently compares source_count to self.progress.get_migrated_count()
(a progress file) instead of the actual target DB; change the validation to
query the target database directly for the entity's record count (e.g., call the
repository/ORM on the target connection or introduce a
_get_target_count(entity_class) method), use that target_count for comparison,
log the detailed mismatch with both counts and context, and keep the
progress-file check only as an additional sanity check if desired.
| def _print_summary(self): | ||
| """Print migration summary.""" | ||
| duration = self.statistics['end_time'] - self.statistics['start_time'] | ||
|
|
||
| self.logger.info("=" * 60) | ||
| self.logger.info("MIGRATION SUMMARY") | ||
| self.logger.info("=" * 60) | ||
| self.logger.info(f"Total entities migrated: {self.statistics['total_entities']}") | ||
| self.logger.info(f"Total records migrated: {self.statistics['total_records']}") | ||
| self.logger.info(f"Duration: {duration:.2f} seconds") | ||
| self.logger.info(f"Average speed: {self.statistics['total_records'] / duration:.1f} records/sec") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential division by zero if migration completes instantly.
If duration is zero (extremely fast migration or clock resolution issues), the average speed calculation will raise ZeroDivisionError.
def _print_summary(self):
"""Print migration summary."""
duration = self.statistics['end_time'] - self.statistics['start_time']
self.logger.info("=" * 60)
self.logger.info("MIGRATION SUMMARY")
self.logger.info("=" * 60)
self.logger.info(f"Total entities migrated: {self.statistics['total_entities']}")
self.logger.info(f"Total records migrated: {self.statistics['total_records']}")
self.logger.info(f"Duration: {duration:.2f} seconds")
- self.logger.info(f"Average speed: {self.statistics['total_records'] / duration:.1f} records/sec")
+ if duration > 0:
+ self.logger.info(f"Average speed: {self.statistics['total_records'] / duration:.1f} records/sec")
+ else:
+ self.logger.info("Average speed: N/A (duration too short to measure)")🤖 Prompt for AI Agents
In data/src/entity_manager/migrate_inheritance.py around lines 416 to 426, the
average speed calculation divides by duration which can be zero and raise
ZeroDivisionError; update the method to guard against zero (or near-zero)
duration by computing a safe_duration = duration if duration > 0 else a small
epsilon (e.g. 1e-6) or by explicitly setting average_speed to 0 or "N/A" when
duration is zero, then use safe_duration (or the conditional value) in the
average calculation and format the output accordingly so the method never
attempts division by zero.
| if args.dry_run: | ||
| print("DRY RUN MODE - No data will be migrated") | ||
| print(f"Source: {migrator.source_connection_string}") | ||
| print(f"Target: {migrator.target_connection_string}") | ||
| print(f"Entity classes to migrate:") | ||
| for entity_class in migrator.entity_classes: | ||
| print(f" - {entity_class.__name__}") | ||
| return |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove extraneous f-string prefix.
Line 500 has an f-string without any placeholders. As flagged by static analysis, the f prefix should be removed.
if args.dry_run:
print("DRY RUN MODE - No data will be migrated")
print(f"Source: {migrator.source_connection_string}")
print(f"Target: {migrator.target_connection_string}")
- print(f"Entity classes to migrate:")
+ print("Entity classes to migrate:")
for entity_class in migrator.entity_classes:
print(f" - {entity_class.__name__}")
return🧰 Tools
🪛 Ruff (0.14.6)
500-500: f-string without any placeholders
Remove extraneous f prefix
(F541)
🤖 Prompt for AI Agents
In data/src/entity_manager/migrate_inheritance.py around lines 496 to 503, the
print on line 500 uses an unnecessary f-string with no placeholders; change
print(f"Entity classes to migrate:") to a normal string print("Entity classes to
migrate:") so the f-prefix is removed while leaving the surrounding dry-run
prints and loop intact.
This pull request adds support for multiple entity inheritance mapping strategies to the entity manager module, making it more flexible in how class hierarchies are mapped to database tables. The main change is the introduction of a new
inheritance_strategiesmodule, which defines different strategies and a factory function for selecting them. The__init__.pyis updated to expose these new strategies and related classes.Entity inheritance mapping improvements:
inheritance_strategies.pymodule that defines three inheritance strategies for mapping entities to database tables:JoinedTableStrategy(default),SingleTableStrategy, andTablePerClassStrategy. Each strategy determines how class hierarchies are represented in the database, offering flexibility for different use cases. A factory functionget_inheritance_strategyis also provided to select the appropriate strategy based on class attributes.Module API enhancements:
entity_manager/__init__.pyto import and expose the new inheritance strategies (InheritanceStrategy,JoinedTableStrategy,SingleTableStrategy,TablePerClassStrategy,get_inheritance_strategy) as part of the public API, along with other related modules such asmapping_strategies,fields,lazy_collections, andquery_builder. [1] [2]Summary by CodeRabbit
New Features
Documentation
Tests
✏️ Tip: You can customize this high-level summary in your review settings.