persistence-kit 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. persistence_kit-0.1.0/LICENSE +21 -0
  2. persistence_kit-0.1.0/PKG-INFO +154 -0
  3. persistence_kit-0.1.0/README.md +123 -0
  4. persistence_kit-0.1.0/persistence_kit/__init__.py +5 -0
  5. persistence_kit-0.1.0/persistence_kit/abstract_repository.py +45 -0
  6. persistence_kit-0.1.0/persistence_kit/abstract_view_repo.py +43 -0
  7. persistence_kit-0.1.0/persistence_kit/config.py +26 -0
  8. persistence_kit-0.1.0/persistence_kit/py.typed +1 -0
  9. persistence_kit-0.1.0/persistence_kit/repository/.idea/inspectionProfiles/profiles_settings.xml +6 -0
  10. persistence_kit-0.1.0/persistence_kit/repository/.idea/misc.xml +4 -0
  11. persistence_kit-0.1.0/persistence_kit/repository/.idea/modules.xml +8 -0
  12. persistence_kit-0.1.0/persistence_kit/repository/.idea/repository.iml +8 -0
  13. persistence_kit-0.1.0/persistence_kit/repository/.idea/workspace.xml +48 -0
  14. persistence_kit-0.1.0/persistence_kit/repository/__init__.py +18 -0
  15. persistence_kit-0.1.0/persistence_kit/repository/filter_ops.py +32 -0
  16. persistence_kit-0.1.0/persistence_kit/repository/memory_repo/__init__.py +3 -0
  17. persistence_kit-0.1.0/persistence_kit/repository/memory_repo/memory_repo.py +163 -0
  18. persistence_kit-0.1.0/persistence_kit/repository/mongo_repo/__init__.py +4 -0
  19. persistence_kit-0.1.0/persistence_kit/repository/mongo_repo/mongo_mapper.py +51 -0
  20. persistence_kit-0.1.0/persistence_kit/repository/mongo_repo/mongo_repo.py +205 -0
  21. persistence_kit-0.1.0/persistence_kit/repository/sqlalchemy_repo/__init__.py +11 -0
  22. persistence_kit-0.1.0/persistence_kit/repository/sqlalchemy_repo/schema_evolve.py +40 -0
  23. persistence_kit-0.1.0/persistence_kit/repository/sqlalchemy_repo/sqlalchemy_dataclass_mapper.py +64 -0
  24. persistence_kit-0.1.0/persistence_kit/repository/sqlalchemy_repo/sqlalchemy_engine.py +11 -0
  25. persistence_kit-0.1.0/persistence_kit/repository/sqlalchemy_repo/sqlalchemy_repo.py +259 -0
  26. persistence_kit-0.1.0/persistence_kit/repository/sqlalchemy_repo/table_factory.py +63 -0
  27. persistence_kit-0.1.0/persistence_kit/repository_factory/.idea/inspectionProfiles/profiles_settings.xml +6 -0
  28. persistence_kit-0.1.0/persistence_kit/repository_factory/.idea/misc.xml +4 -0
  29. persistence_kit-0.1.0/persistence_kit/repository_factory/.idea/modules.xml +8 -0
  30. persistence_kit-0.1.0/persistence_kit/repository_factory/.idea/repository_factory.iml +8 -0
  31. persistence_kit-0.1.0/persistence_kit/repository_factory/.idea/workspace.xml +49 -0
  32. persistence_kit-0.1.0/persistence_kit/repository_factory/__init__.py +12 -0
  33. persistence_kit-0.1.0/persistence_kit/repository_factory/entity_registry.py +38 -0
  34. persistence_kit-0.1.0/persistence_kit/repository_factory/populating_repo.py +163 -0
  35. persistence_kit-0.1.0/persistence_kit/repository_factory/repo_factory.py +113 -0
  36. persistence_kit-0.1.0/pyproject.toml +36 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Andres Felipe Serrano Barrios
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,154 @@
1
+ Metadata-Version: 2.4
2
+ Name: persistence-kit
3
+ Version: 0.1.0
4
+ Summary: Reusable persistence and repository toolkit
5
+ License: MIT
6
+ License-File: LICENSE
7
+ Keywords: repository,persistence,mongodb,sqlalchemy,async
8
+ Author: Andres Felipe Serrano Barrios
9
+ Author-email: andresfserrano1@gmail.com
10
+ Requires-Python: >=3.11,<4.0
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Topic :: Database
20
+ Classifier: Typing :: Typed
21
+ Requires-Dist: asyncpg (>=0.30.0,<0.31.0)
22
+ Requires-Dist: motor (>=3.7.1,<4.0.0)
23
+ Requires-Dist: pydantic-settings (>=2.3.0,<3.0.0)
24
+ Requires-Dist: sqlalchemy[asyncio] (>=2.0.43,<3.0.0)
25
+ Requires-Dist: typing-extensions (>=4.12.0,<5.0.0)
26
+ Project-URL: Documentation, https://github.com/andresfserrano/persistence-kit#readme
27
+ Project-URL: Homepage, https://pypi.org/project/persistence-kit/
28
+ Project-URL: Repository, https://github.com/andresfserrano/persistence-kit
29
+ Description-Content-Type: text/markdown
30
+
31
+ # persistence-kit
32
+
33
+ Reusable persistence toolkit with async repository implementations for:
34
+
35
+ - `memory`
36
+ - `mongo` (Motor)
37
+ - `postgres` (SQLAlchemy async + asyncpg)
38
+
39
+ Author: Andres Felipe Serrano Barrios
40
+
41
+ ## Installation
42
+
43
+ ```bash
44
+ pip install persistence-kit
45
+ ```
46
+
47
+ ## Quick Start
48
+
49
+ ```python
50
+ from persistence_kit.repository_factory import register_entity, get_repo
51
+ from persistence_kit.config import Database
52
+
53
+ # register entities during application startup
54
+ register_entity(
55
+ "user",
56
+ {
57
+ "entity": User,
58
+ "collection": "users",
59
+ "database": Database.MEMORY,
60
+ "unique": {"email": "email"},
61
+ },
62
+ )
63
+
64
+ repo = get_repo("user")
65
+ ```
66
+
67
+ ## Supported Environment Variables
68
+
69
+ - `REPO_DATABASE=memory|mongo|postgres`
70
+ - `MONGO_DSN`
71
+ - `MONGO_DB`
72
+ - `POSTGRES_USER`
73
+ - `POSTGRES_PASSWORD`
74
+ - `POSTGRES_HOST`
75
+ - `POSTGRES_PORT`
76
+ - `POSTGRES_DB`
77
+
78
+ ## Publish to PyPI (Manual)
79
+
80
+ ```bash
81
+ python -m pip install --upgrade build twine
82
+ python -m build
83
+ python -m twine check dist/*
84
+ python -m twine upload dist/*
85
+ ```
86
+
87
+ ## Automated Publish via GitHub Actions
88
+
89
+ This repository includes a workflow at `.github/workflows/publish-pypi.yml`.
90
+
91
+ It publishes to PyPI when:
92
+
93
+ - a GitHub Release is published
94
+ - the release tag points to the current `main` HEAD
95
+
96
+ Prerequisite:
97
+
98
+ - Configure PyPI Trusted Publishing for this repository and workflow file.
99
+
100
+ ## Preview Releases Without PRs
101
+
102
+ Use `.github/workflows/publish-preview.yml` to publish directly from GitHub Actions
103
+ without merging a PR.
104
+
105
+ How it works:
106
+
107
+ - Trigger `Publish Preview Package` manually from the Actions tab.
108
+ - Enter a `version` (for example `0.1.1.dev1` or `0.1.2.dev1`).
109
+ - Choose target repository: `testpypi` (recommended) or `pypi`.
110
+ - The workflow patches `pyproject.toml` version only inside the CI run, builds, and publishes.
111
+ - No commit and no PR are required for this preview publish flow.
112
+
113
+ Important:
114
+
115
+ - Prefer `*.devN` versions for preview builds.
116
+ - PyPI/TestPyPI do not allow re-uploading the same file version.
117
+
118
+ ## Local Test Releases (No PR Required)
119
+
120
+ If you want to test changes from your machine in external projects without opening a PR,
121
+ publish a prerelease from local code to TestPyPI.
122
+
123
+ ### 1. Create a TestPyPI token
124
+
125
+ - Create an API token in TestPyPI.
126
+ - Export it as environment variable:
127
+
128
+ ```bash
129
+ export TWINE_PASSWORD="pypi-***"
130
+ ```
131
+
132
+ ### 2. Publish from local code
133
+
134
+ ```bash
135
+ bash ./scripts/publish-local.sh 0.1.1.dev1 testpypi
136
+ ```
137
+
138
+ You can publish another local iteration with a new version:
139
+
140
+ ```bash
141
+ bash ./scripts/publish-local.sh 0.1.1.dev2 testpypi
142
+ ```
143
+
144
+ ### 3. Install from external projects
145
+
146
+ ```bash
147
+ pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple persistence-kit==0.1.1.dev1
148
+ ```
149
+
150
+ ## Recommended Release Strategy
151
+
152
+ - Local experimental testing: publish `0.x.y.devN` to TestPyPI from local machine.
153
+ - Official release: publish `0.x.y` to PyPI through GitHub Release (`publish-pypi.yml`).
154
+
@@ -0,0 +1,123 @@
1
+ # persistence-kit
2
+
3
+ Reusable persistence toolkit with async repository implementations for:
4
+
5
+ - `memory`
6
+ - `mongo` (Motor)
7
+ - `postgres` (SQLAlchemy async + asyncpg)
8
+
9
+ Author: Andres Felipe Serrano Barrios
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ pip install persistence-kit
15
+ ```
16
+
17
+ ## Quick Start
18
+
19
+ ```python
20
+ from persistence_kit.repository_factory import register_entity, get_repo
21
+ from persistence_kit.config import Database
22
+
23
+ # register entities during application startup
24
+ register_entity(
25
+ "user",
26
+ {
27
+ "entity": User,
28
+ "collection": "users",
29
+ "database": Database.MEMORY,
30
+ "unique": {"email": "email"},
31
+ },
32
+ )
33
+
34
+ repo = get_repo("user")
35
+ ```
36
+
37
+ ## Supported Environment Variables
38
+
39
+ - `REPO_DATABASE=memory|mongo|postgres`
40
+ - `MONGO_DSN`
41
+ - `MONGO_DB`
42
+ - `POSTGRES_USER`
43
+ - `POSTGRES_PASSWORD`
44
+ - `POSTGRES_HOST`
45
+ - `POSTGRES_PORT`
46
+ - `POSTGRES_DB`
47
+
48
+ ## Publish to PyPI (Manual)
49
+
50
+ ```bash
51
+ python -m pip install --upgrade build twine
52
+ python -m build
53
+ python -m twine check dist/*
54
+ python -m twine upload dist/*
55
+ ```
56
+
57
+ ## Automated Publish via GitHub Actions
58
+
59
+ This repository includes a workflow at `.github/workflows/publish-pypi.yml`.
60
+
61
+ It publishes to PyPI when:
62
+
63
+ - a GitHub Release is published
64
+ - the release tag points to the current `main` HEAD
65
+
66
+ Prerequisite:
67
+
68
+ - Configure PyPI Trusted Publishing for this repository and workflow file.
69
+
70
+ ## Preview Releases Without PRs
71
+
72
+ Use `.github/workflows/publish-preview.yml` to publish directly from GitHub Actions
73
+ without merging a PR.
74
+
75
+ How it works:
76
+
77
+ - Trigger `Publish Preview Package` manually from the Actions tab.
78
+ - Enter a `version` (for example `0.1.1.dev1` or `0.1.2.dev1`).
79
+ - Choose target repository: `testpypi` (recommended) or `pypi`.
80
+ - The workflow patches `pyproject.toml` version only inside the CI run, builds, and publishes.
81
+ - No commit and no PR are required for this preview publish flow.
82
+
83
+ Important:
84
+
85
+ - Prefer `*.devN` versions for preview builds.
86
+ - PyPI/TestPyPI do not allow re-uploading the same file version.
87
+
88
+ ## Local Test Releases (No PR Required)
89
+
90
+ If you want to test changes from your machine in external projects without opening a PR,
91
+ publish a prerelease from local code to TestPyPI.
92
+
93
+ ### 1. Create a TestPyPI token
94
+
95
+ - Create an API token in TestPyPI.
96
+ - Export it as environment variable:
97
+
98
+ ```bash
99
+ export TWINE_PASSWORD="pypi-***"
100
+ ```
101
+
102
+ ### 2. Publish from local code
103
+
104
+ ```bash
105
+ bash ./scripts/publish-local.sh 0.1.1.dev1 testpypi
106
+ ```
107
+
108
+ You can publish another local iteration with a new version:
109
+
110
+ ```bash
111
+ bash ./scripts/publish-local.sh 0.1.1.dev2 testpypi
112
+ ```
113
+
114
+ ### 3. Install from external projects
115
+
116
+ ```bash
117
+ pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple persistence-kit==0.1.1.dev1
118
+ ```
119
+
120
+ ## Recommended Release Strategy
121
+
122
+ - Local experimental testing: publish `0.x.y.devN` to TestPyPI from local machine.
123
+ - Official release: publish `0.x.y` to PyPI through GitHub Release (`publish-pypi.yml`).
@@ -0,0 +1,5 @@
1
+ from .abstract_repository import Repository
2
+ from .abstract_view_repo import ViewRepository
3
+ from .config import Database, RepoSettings
4
+
5
+ __all__ = ["Repository", "ViewRepository", "Database", "RepoSettings"]
@@ -0,0 +1,45 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC, abstractmethod
4
+
5
+ from typing import Optional, Sequence, TypeVar, Generic, Hashable, Mapping, Any
6
+
7
+ T = TypeVar("T")
8
+ TId = TypeVar("TId", bound=Hashable)
9
+
10
+ class Repository(ABC, Generic[T, TId]):
11
+ @abstractmethod
12
+ async def add(self, entity: T) -> None: ...
13
+
14
+ @abstractmethod
15
+ async def get(self, entity_id: TId) -> Optional[T]: ...
16
+
17
+ @abstractmethod
18
+ async def list(
19
+ self,
20
+ *,
21
+ offset: int = 0,
22
+ limit: int = 50,
23
+ sort_by: str | None = None,
24
+ sort_desc: bool = False,
25
+ ) -> Sequence[T]: ...
26
+
27
+ @abstractmethod
28
+ async def update(self, entity: T) -> None: ...
29
+
30
+ @abstractmethod
31
+ async def delete(self, entity_id: TId) -> None: ...
32
+
33
+ @abstractmethod
34
+ async def get_by_index(self, index: str, value: Hashable) -> Optional[T]: ...
35
+
36
+ @abstractmethod
37
+ async def list_by_fields(
38
+ self,
39
+ criteria: Mapping[str, Hashable | list[Hashable] | Mapping[str, Any]],
40
+ *,
41
+ offset: int = 0,
42
+ limit: Optional[int] = 50,
43
+ sort_by: str | None = None,
44
+ sort_desc: bool = False,
45
+ ) -> Sequence[T]: ...
@@ -0,0 +1,43 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC, abstractmethod
4
+
5
+ from typing import Optional, Sequence, TypeVar, Generic, Hashable, Mapping, Any
6
+
7
+ T = TypeVar("T")
8
+ TId = TypeVar("TId", bound=Hashable)
9
+
10
+ class ViewRepository(ABC, Generic[T, TId]):
11
+ @abstractmethod
12
+ async def get_with(self, entity_id: TId, include: Sequence[str]) -> Optional[dict]: ...
13
+
14
+ @abstractmethod
15
+ async def get_by_index_with(
16
+ self,
17
+ index: str,
18
+ value: Hashable,
19
+ include: Sequence[str],
20
+ ) -> Optional[dict]: ...
21
+
22
+ @abstractmethod
23
+ async def list_with(
24
+ self,
25
+ *,
26
+ offset: int = 0,
27
+ limit: int = 50,
28
+ include: Sequence[str] = (),
29
+ sort_by: str | None = None,
30
+ sort_desc: bool = False,
31
+ ) -> list[dict]: ...
32
+
33
+ @abstractmethod
34
+ async def list_by_fields(
35
+ self,
36
+ criteria: Mapping[str, Hashable | list[Hashable] | Mapping[str, Any]],
37
+ *,
38
+ offset: int = 0,
39
+ limit: Optional[int] = 50,
40
+ include: Sequence[str] = (),
41
+ sort_by: str | None = None,
42
+ sort_desc: bool = False,
43
+ ) -> list[dict]: ...
@@ -0,0 +1,26 @@
1
+ from enum import Enum
2
+
3
+ from pydantic_settings import BaseSettings, SettingsConfigDict
4
+
5
+
6
+ class Database(str, Enum):
7
+ MEMORY = "memory"
8
+ MONGO = "mongo"
9
+ POSTGRES = "postgres"
10
+
11
+
12
+ class RepoSettings(BaseSettings):
13
+ repo_database: Database = Database.MEMORY
14
+
15
+ mongo_dsn: str | None = None
16
+ mongo_db: str | None = None
17
+
18
+ postgres_user: str | None = None
19
+ postgres_password: str | None = None
20
+ postgres_host: str | None = None
21
+ postgres_port: int | None = 5432
22
+ postgres_db: str | None = None
23
+
24
+ # Keep the library environment-agnostic. Host applications decide
25
+ # whether settings come from .env, process env vars, secret stores, etc.
26
+ model_config = SettingsConfigDict(extra="ignore")
@@ -0,0 +1,6 @@
1
+ <component name="InspectionProjectProfileManager">
2
+ <settings>
3
+ <option name="USE_PROJECT_PROFILE" value="false" />
4
+ <version value="1.0" />
5
+ </settings>
6
+ </component>
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.11 virtualenv at C:\Users\andre\OneDrive\Escritorio\SIGA\Backend\.venv" project-jdk-type="Python SDK" />
4
+ </project>
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectModuleManager">
4
+ <modules>
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/repository.iml" filepath="$PROJECT_DIR$/.idea/repository.iml" />
6
+ </modules>
7
+ </component>
8
+ </project>
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module type="PYTHON_MODULE" version="4">
3
+ <component name="NewModuleRootManager">
4
+ <content url="file://$MODULE_DIR$" />
5
+ <orderEntry type="jdk" jdkName="Python 3.11 virtualenv at C:\Users\andre\OneDrive\Escritorio\SIGA\Backend\.venv" jdkType="Python SDK" />
6
+ <orderEntry type="sourceFolder" forTests="false" />
7
+ </component>
8
+ </module>
@@ -0,0 +1,48 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ChangeListManager">
4
+ <list default="true" id="3353231d-cbff-4250-b6f6-e702a244cb73" name="Changes" comment="" />
5
+ <option name="SHOW_DIALOG" value="false" />
6
+ <option name="HIGHLIGHT_CONFLICTS" value="true" />
7
+ <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
8
+ <option name="LAST_RESOLUTION" value="IGNORE" />
9
+ </component>
10
+ <component name="ProjectColorInfo"><![CDATA[{
11
+ "associatedIndex": 7
12
+ }]]></component>
13
+ <component name="ProjectId" id="3A5AquY2AKyR9xwjoFWX4R6KC9b" />
14
+ <component name="ProjectViewState">
15
+ <option name="hideEmptyMiddlePackages" value="true" />
16
+ <option name="showLibraryContents" value="true" />
17
+ </component>
18
+ <component name="PropertiesComponent"><![CDATA[{
19
+ "keyToString": {
20
+ "ModuleVcsDetector.initialDetectionPerformed": "true",
21
+ "RunOnceActivity.ShowReadmeOnStart": "true",
22
+ "last_opened_file_path": "C:/Users/andre/OneDrive/Escritorio/aux programación udea/persistence_kit/repository",
23
+ "vue.rearranger.settings.migration": "true"
24
+ }
25
+ }]]></component>
26
+ <component name="SharedIndexes">
27
+ <attachedChunks>
28
+ <set>
29
+ <option value="bundled-js-predefined-d6986cc7102b-9c94529fcfe0-JavaScript-PY-252.26199.168" />
30
+ <option value="bundled-python-sdk-b3d66beaba9a-c6efb3732140-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-252.26199.168" />
31
+ </set>
32
+ </attachedChunks>
33
+ </component>
34
+ <component name="TaskManager">
35
+ <task active="true" id="Default" summary="Default task">
36
+ <changelist id="3353231d-cbff-4250-b6f6-e702a244cb73" name="Changes" comment="" />
37
+ <created>1771872251986</created>
38
+ <option name="number" value="Default" />
39
+ <option name="presentableId" value="Default" />
40
+ <updated>1771872251986</updated>
41
+ <workItem from="1771872254661" duration="18000" />
42
+ </task>
43
+ <servers />
44
+ </component>
45
+ <component name="TypeScriptGeneratedFilesManager">
46
+ <option name="version" value="3" />
47
+ </component>
48
+ </project>
@@ -0,0 +1,18 @@
1
+ from .memory_repo import MemoryRepository
2
+ from .mongo_repo import DataclassMapper, MongoRepository
3
+ from .sqlalchemy_repo import (
4
+ SqlAlchemyRepository,
5
+ SqlDataclassMapper,
6
+ build_table_from_dataclass,
7
+ get_engine,
8
+ )
9
+
10
+ __all__ = [
11
+ "MemoryRepository",
12
+ "MongoRepository",
13
+ "DataclassMapper",
14
+ "SqlAlchemyRepository",
15
+ "SqlDataclassMapper",
16
+ "build_table_from_dataclass",
17
+ "get_engine",
18
+ ]
@@ -0,0 +1,32 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Iterable, Mapping
4
+
5
+
6
+ _SIMPLE_OPS = {"gte", "gt", "lte", "lt", "eq", "ne"}
7
+
8
+
9
+ def is_multi_value(value: Any) -> bool:
10
+ return isinstance(value, list)
11
+
12
+
13
+ def is_range_dict(value: Any) -> bool:
14
+ return isinstance(value, dict)
15
+
16
+
17
+ def iter_range_ops(value: Mapping[str, Any]) -> Iterable[tuple[str, Any]]:
18
+ if not value:
19
+ return []
20
+ items = list(value.items())
21
+ for op, v in items:
22
+ if op == "between":
23
+ if not isinstance(v, list) or len(v) != 2:
24
+ raise ValueError("between expects a list with exactly two values")
25
+ elif op == "in":
26
+ if not isinstance(v, list):
27
+ raise ValueError("in expects a list")
28
+ elif op in _SIMPLE_OPS:
29
+ continue
30
+ else:
31
+ raise ValueError(f"Unsupported operator: {op}")
32
+ return items
@@ -0,0 +1,3 @@
1
+ from .memory_repo import MemoryRepository
2
+
3
+ __all__ = ["MemoryRepository"]