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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ jobs:
env:
HTTPBIN: httpbin.bemisc.com
PLUGIN_PATH: ./*/src;./*/*src
- run: |
pip install pytest
pytest scripts/test/ -v
if: matrix.python-version != '2.7'
build-pypy:
name: Build PyPy
timeout-minutes: 10
Expand Down Expand Up @@ -95,6 +99,9 @@ jobs:
env:
HTTPBIN: httpbin.bemisc.com
PLUGIN_PATH: ./*/src;./*/*src
- run: |
pip install pytest
pytest scripts/test/ -v
build-pypy-arm:
name: Build PyPy ARM
timeout-minutes: 10
Expand Down Expand Up @@ -146,11 +153,14 @@ jobs:
- run: |
pip install black
black . --check
if: matrix.python-version == '3.13'
if: matrix.python-version == '3.14'
- run: python setup.py test
env:
HTTPBIN: httpbin.bemisc.com
PLUGIN_PATH: ./*/src;./*/*src
- run: |
pip install pytest
pytest scripts/test/ -v
build-macos:
name: Build macOS
timeout-minutes: 10
Expand All @@ -175,8 +185,11 @@ jobs:
- run: |
pip install black
black . --check
if: matrix.python-version == '3.13'
if: matrix.python-version == '3.14'
- run: python setup.py test
env:
HTTPBIN: httpbin.bemisc.com
PLUGIN_PATH: ./*/src;./*/*src
- run: |
pip install pytest
pytest scripts/test/ -v
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

* Concrete Table Inheritance (CTI per-class) as an alternative inheritance strategy for entity hierarchies, selectable via `inheritance = "concrete_table"` class attribute
* New `get_inheritance_strategy()` and `is_concrete_table()` classmethods on `EntityClass` for strategy resolution across the hierarchy
* New `get_all_items()` classmethod on `EntityClass` that returns all items (own + inherited) flattened into a single dictionary for concrete table support
* Concrete table support in query generation: `_create_definition_query`, `_save_query`, `_update_query`, `_remove_query`, and `_find_query` branch on inheritance strategy to produce single-table operations
* Standalone migration script (`scripts/migrate_inheritance.py`) for converting entity hierarchies between `class_table` and `concrete_table` strategies with backup, validation, dry-run, and transactional execution
* Test entities (`ConcreteRootEntity`, `ConcretePerson`, `ConcreteEmployee`, `ConcreteAddress`) and unit tests for the concrete table inheritance strategy
* New `get_connection_address()` method in `RESTRequest` with proxy header resolution (`X-Forwarded-For`, `X-Client-IP`, `X-Real-IP`) and IPv6-mapped IPv4 cleanup (`::ffff:` prefix removal)
* New `get_connection_address()` method in `HTTPRequest` and `WSGIRequest` to provide a uniform interface for retrieving client connection address
* Optional `resolve` and `cleanup` parameters in `RESTRequest.get_address()` for controlling proxy resolution and IPv6 cleanup
Expand All @@ -26,6 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

* Model logging methods (`debug`, `info`, `warning`, `error`, `critical`) and their class-level counterparts now accept `*args` and `**kwargs` for lazy evaluation support
* Service polling in `service_utils` now auto-selects the best available mechanism: `epoll` on Linux, `kqueue` on BSD/macOS, `poll` on other Unix systems, and `select` as fallback
* Implemented `EpollPolling` using `select.epoll()`, `KqueuePolling` using `select.kqueue()`, and `Epoll2Polling` using `select.poll()` as alternatives to `SelectPolling`, removing the 1024 file descriptor limit on supported platforms
* Client I/O polling in `client_utils` now uses the same platform-aware strategy via a new `poll_socket()` function, replacing direct `select.select()` calls in `_receive()` and `_send()` and removing the 1024 fd limit for epoll/kqueue/poll backends while preserving the guard for the `select` fallback
Expand Down
146 changes: 146 additions & 0 deletions data/src/entity_manager/mocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,152 @@ def __init__(self):
self.legs = 4


class ConcreteRootEntity(structures.EntityClass):
"""
The concrete root entity class, this class represents
a typical base class for a model hierarchy using the
concrete table inheritance strategy.
"""

inheritance = "concrete_table"
""" Concrete table inheritance strategy, each concrete
class stores all attributes in a single table """

object_id = dict(id=True, type="integer", generated=True)
""" The object id of the concrete root entity """

status = dict(type="integer")
""" The status of the entity (1-enabled, 2-disabled) """

metadata = dict(type="metadata")
""" Simple metadata value that is going to be used
for storage of structured data (maps and lists) """

def __init__(self):
"""
Constructor of the class.
"""

self.object_id = None
self.status = 1


class ConcretePerson(ConcreteRootEntity):
"""
The concrete person entity class, represents the set of
typical attributes of a person using concrete table
inheritance.
"""

name = dict(type="text")
""" The name of the person """

age = dict(type="integer")
""" The age of the person """

weight = dict(type="decimal")
""" The weight of the person """

parent = dict(type="relation")
""" The parent for the current person """

children = dict(type="relation")
""" The children of the current person """

address = dict(type="relation")
""" The address associated with the person """

def __init__(self):
"""
Constructor of the class.
"""

ConcreteRootEntity.__init__(self)
self.name = "Anonymous"
self.age = 18

@staticmethod
def _relation_parent():
return dict(
type="to-one", target=ConcretePerson, reverse="children", is_mapper=True
)

@staticmethod
def _relation_children():
return dict(type="to-many", target=ConcretePerson, reverse="parent")

@staticmethod
def _relation_address():
return dict(
type="to-one",
target=ConcreteAddress,
reverse="person",
is_mapper=True,
)


class ConcreteEmployee(ConcretePerson):
"""
The concrete employee entity class, the set of attributes
contained in this class should be able to represent
an employee using concrete table inheritance.
"""

salary = dict(type="integer")
""" The salary of the employee """

boss = dict(type="relation")
""" The boss of the employee (only one is allowed) """

def __init__(self):
"""
Constructor of the class.
"""

ConcretePerson.__init__(self)
self.salary = 200

@staticmethod
def _relation_boss():
return dict(
type="to-one", target=ConcretePerson, reverse="children", is_mapper=True
)
Comment on lines +539 to +543
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

boss is paired with the wrong reverse relation.

Using reverse="children" ties manager lookups to the parent/child relation metadata. ConcretePerson needs a dedicated report collection here; otherwise reverse traversal and lazy loading for boss relationships will resolve the wrong side.

🐛 Suggested fix
 class ConcretePerson(ConcreteRootEntity):
@@
     children = dict(type="relation")
     """ The children of the current person """
 
+    employees = dict(type="relation")
+    """ The employees associated with the person """
+
@@
     def _relation_children():
         return dict(type="to-many", target=ConcretePerson, reverse="parent")
 
+    `@staticmethod`
+    def _relation_employees():
+        return dict(type="to-many", target=ConcreteEmployee, reverse="boss")
+
@@
 class ConcreteEmployee(ConcretePerson):
@@
     `@staticmethod`
     def _relation_boss():
         return dict(
-            type="to-one", target=ConcretePerson, reverse="children", is_mapper=True
+            type="to-one", target=ConcretePerson, reverse="employees", is_mapper=True
         )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@data/src/entity_manager/mocks.py` around lines 539 - 543, _relation_boss
currently sets reverse="children", which pairs the boss relation to the
parent/child metadata; change the reverse to a dedicated reports collection
(e.g., reverse="reports") so boss lookups and lazy loading traverse the correct
relation on ConcretePerson, and ensure ConcretePerson defines a matching reports
relation name; update the dict returned by _relation_boss to use
reverse="reports" (or the actual reports relation name on ConcretePerson).



class ConcreteAddress(ConcreteRootEntity):
"""
The concrete address entity class, representing the typical
set of attributes for a postal address using concrete table
inheritance.
"""

street = dict(type="text")
""" The street of the address """

number = dict(type="integer")
""" The door number of the address """

country = dict(type="text")
""" The country of the address """

person = dict(type="relation")
""" The person associated with the address """

def __init__(self):
"""
Constructor for the class.
"""

ConcreteRootEntity.__init__(self)
self.street = "N/A"
self.number = 0
self.country = "N/A"

@staticmethod
def _relation_person():
return dict(type="to-one", target=ConcretePerson, reverse="address")


class File(RootEntity):
"""
The file entity class, that represent a typical file
Expand Down
Loading
Loading