strawberry-alchemy 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 (38) hide show
  1. strawberry_alchemy-0.1.0/LICENSE +21 -0
  2. strawberry_alchemy-0.1.0/PKG-INFO +126 -0
  3. strawberry_alchemy-0.1.0/README.md +95 -0
  4. strawberry_alchemy-0.1.0/pyproject.toml +71 -0
  5. strawberry_alchemy-0.1.0/src/strawberry_alchemy/__init__.py +124 -0
  6. strawberry_alchemy-0.1.0/src/strawberry_alchemy/enums.py +9 -0
  7. strawberry_alchemy-0.1.0/src/strawberry_alchemy/exceptions.py +2 -0
  8. strawberry_alchemy-0.1.0/src/strawberry_alchemy/filtering/__init__.py +23 -0
  9. strawberry_alchemy-0.1.0/src/strawberry_alchemy/filtering/access_control.py +19 -0
  10. strawberry_alchemy-0.1.0/src/strawberry_alchemy/filtering/filter_builder.py +322 -0
  11. strawberry_alchemy-0.1.0/src/strawberry_alchemy/filtering/inputs.py +67 -0
  12. strawberry_alchemy-0.1.0/src/strawberry_alchemy/filtering/operators.py +23 -0
  13. strawberry_alchemy-0.1.0/src/strawberry_alchemy/mapping/__init__.py +19 -0
  14. strawberry_alchemy-0.1.0/src/strawberry_alchemy/mapping/sqlalchemy_to_gql.py +192 -0
  15. strawberry_alchemy-0.1.0/src/strawberry_alchemy/models/__init__.py +3 -0
  16. strawberry_alchemy-0.1.0/src/strawberry_alchemy/models/base.py +32 -0
  17. strawberry_alchemy-0.1.0/src/strawberry_alchemy/optimizer/__init__.py +38 -0
  18. strawberry_alchemy-0.1.0/src/strawberry_alchemy/optimizer/prefetch.py +246 -0
  19. strawberry_alchemy-0.1.0/src/strawberry_alchemy/optimizer/query_analyzer.py +198 -0
  20. strawberry_alchemy-0.1.0/src/strawberry_alchemy/optimizer/query_optimizer.py +920 -0
  21. strawberry_alchemy-0.1.0/src/strawberry_alchemy/permissions/__init__.py +41 -0
  22. strawberry_alchemy-0.1.0/src/strawberry_alchemy/permissions/base.py +114 -0
  23. strawberry_alchemy-0.1.0/src/strawberry_alchemy/permissions/checker.py +30 -0
  24. strawberry_alchemy-0.1.0/src/strawberry_alchemy/permissions/input_parser.py +57 -0
  25. strawberry_alchemy-0.1.0/src/strawberry_alchemy/permissions/resolver.py +13 -0
  26. strawberry_alchemy-0.1.0/src/strawberry_alchemy/permissions/resource_bag.py +31 -0
  27. strawberry_alchemy-0.1.0/src/strawberry_alchemy/permissions/types.py +24 -0
  28. strawberry_alchemy-0.1.0/src/strawberry_alchemy/py.typed +0 -0
  29. strawberry_alchemy-0.1.0/src/strawberry_alchemy/repository/__init__.py +11 -0
  30. strawberry_alchemy-0.1.0/src/strawberry_alchemy/repository/base.py +255 -0
  31. strawberry_alchemy-0.1.0/src/strawberry_alchemy/repository/deletion.py +52 -0
  32. strawberry_alchemy-0.1.0/src/strawberry_alchemy/schema/__init__.py +3 -0
  33. strawberry_alchemy-0.1.0/src/strawberry_alchemy/schema/base.py +39 -0
  34. strawberry_alchemy-0.1.0/src/strawberry_alchemy/types/__init__.py +17 -0
  35. strawberry_alchemy-0.1.0/src/strawberry_alchemy/types/base_node.py +89 -0
  36. strawberry_alchemy-0.1.0/src/strawberry_alchemy/types/connection.py +180 -0
  37. strawberry_alchemy-0.1.0/src/strawberry_alchemy/types/list_result.py +7 -0
  38. strawberry_alchemy-0.1.0/src/strawberry_alchemy/utils.py +21 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alteian
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,126 @@
1
+ Metadata-Version: 2.4
2
+ Name: strawberry-alchemy
3
+ Version: 0.1.0
4
+ Summary: Toolkit for fast and easy strawberry+sqlalchemy apis
5
+ Keywords: graphql,strawberry,sqlalchemy,async,api,pagination,relay,filtering,permissions,crud,database
6
+ Author: David Roučka
7
+ Author-email: David Roučka <Alteian@proton.me>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Framework :: AsyncIO
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Topic :: Software Development :: Libraries
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Classifier: Typing :: Typed
21
+ Requires-Dist: strawberry-graphql>=0.220
22
+ Requires-Dist: sqlalchemy
23
+ Requires-Dist: pydantic>=2.0
24
+ Requires-Dist: python-dateutil>=2.7
25
+ Requires-Python: >=3.13
26
+ Project-URL: Homepage, https://github.com/Alteian/strawberry-alchemy
27
+ Project-URL: Repository, https://github.com/Alteian/strawberry-alchemy
28
+ Project-URL: Issues, https://github.com/Alteian/strawberry-alchemy/issues
29
+ Project-URL: Changelog, https://github.com/Alteian/strawberry-alchemy/blob/master/CHANGELOG.md
30
+ Description-Content-Type: text/markdown
31
+
32
+ # strawberry-alchemy
33
+
34
+ <p align="center">
35
+ <em>Batteries-included toolkit for building <strong>Strawberry GraphQL</strong> APIs backed by <strong>SQLAlchemy</strong></em>
36
+ </p>
37
+
38
+ <p align="center">
39
+ <a href="https://github.com/Alteian/strawberry-alchemy/actions/workflows/ci.yml"><img src="https://github.com/Alteian/strawberry-alchemy/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
40
+ <a href="https://pypi.org/project/strawberry-alchemy"><img src="https://img.shields.io/pypi/v/strawberry-alchemy?color=%2334D058&label=pypi" alt="PyPI version"></a>
41
+ <a href="https://pypi.org/project/strawberry-alchemy"><img src="https://img.shields.io/pypi/pyversions/strawberry-alchemy.svg?color=%2334D058" alt="Python versions"></a>
42
+ <a href="https://github.com/Alteian/strawberry-alchemy/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-MIT-yellow.svg" alt="License: MIT"></a>
43
+ </p>
44
+
45
+ ---
46
+
47
+ **Source Code**: [https://github.com/Alteian/strawberry-alchemy](https://github.com/Alteian/strawberry-alchemy)
48
+
49
+ ---
50
+
51
+ ## Features
52
+
53
+ | Module | What it does |
54
+ |---|---|
55
+ | **QueryOptimizer** | Analyzes Strawberry selection sets and builds a single optimized SQLAlchemy query — automatic `joinedload` / `selectinload`, column deferral, annotation injection |
56
+ | **FilterBuilder** | Translates Strawberry input types into SQLAlchemy `WHERE` clauses using a declarative operator system |
57
+ | **Repository** | Generic async CRUD with delete, dependent-map cascading, and lifecycle hooks |
58
+ | **Types** | Relay `Connection` / `Edge` / `PageInfo` pagination, `ListResult`, `BaseNodeType` |
59
+ | **Mapping** | Async helpers to convert SQLAlchemy instances → Strawberry types respecting the selected field tree |
60
+ | **Permissions** | Protocol-based permission primitives: `IsAuthenticated`, `RolePermission`, `OwnerPermission`, `ObjectAccessPermission`, plus resolver pattern and resource-bag |
61
+ | **Models** | Tiny SQLAlchemy `DeclarativeBase` with UUID primary key, timestamps, and automatic table naming |
62
+ | **Utilities** | `camel_to_snake`, `DateTimeProcessor`, `Ordering` enum, common exceptions |
63
+
64
+ ## Installation
65
+
66
+ ```bash
67
+ pip install strawberry-alchemy
68
+ # or with uv
69
+ uv add strawberry-alchemy
70
+ ```
71
+
72
+ ## Quick Start
73
+
74
+ ```python
75
+ import strawberry
76
+ from sqlalchemy.ext.asyncio import AsyncSession
77
+
78
+ from strawberry_alchemy import (
79
+ BaseNodeType,
80
+ BaseRepository,
81
+ FilterBuilder,
82
+ OptimizedListConnection,
83
+ QueryOptimizer,
84
+ )
85
+
86
+ # 1. Define your SQLAlchemy model (or use the provided Base)
87
+ from strawberry_alchemy.models import Base
88
+
89
+ # 2. Define your Strawberry type
90
+ @strawberry.type
91
+ class BookType(BaseNodeType):
92
+ title: str
93
+ author: str
94
+
95
+ # 3. Use QueryOptimizer in your resolver
96
+ @strawberry.type
97
+ class Query:
98
+ @strawberry.field
99
+ async def books(self, info: strawberry.Info) -> list[BookType]:
100
+ optimizer = QueryOptimizer(session=info.context.db, info=info)
101
+ result = await optimizer.optimize_query(model=Book)
102
+ return result.items
103
+ ```
104
+
105
+ ## Development
106
+
107
+ ```bash
108
+ git clone https://github.com/Alteian/strawberry-alchemy.git
109
+ cd strawberry-alchemy
110
+ uv sync
111
+
112
+ # Lint & test
113
+ uv run ruff check .
114
+ uv run pytest -v
115
+
116
+ # Build
117
+ uv build
118
+ ```
119
+
120
+ ## Contributing
121
+
122
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
123
+
124
+ ## License
125
+
126
+ [MIT](LICENSE)
@@ -0,0 +1,95 @@
1
+ # strawberry-alchemy
2
+
3
+ <p align="center">
4
+ <em>Batteries-included toolkit for building <strong>Strawberry GraphQL</strong> APIs backed by <strong>SQLAlchemy</strong></em>
5
+ </p>
6
+
7
+ <p align="center">
8
+ <a href="https://github.com/Alteian/strawberry-alchemy/actions/workflows/ci.yml"><img src="https://github.com/Alteian/strawberry-alchemy/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
9
+ <a href="https://pypi.org/project/strawberry-alchemy"><img src="https://img.shields.io/pypi/v/strawberry-alchemy?color=%2334D058&label=pypi" alt="PyPI version"></a>
10
+ <a href="https://pypi.org/project/strawberry-alchemy"><img src="https://img.shields.io/pypi/pyversions/strawberry-alchemy.svg?color=%2334D058" alt="Python versions"></a>
11
+ <a href="https://github.com/Alteian/strawberry-alchemy/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-MIT-yellow.svg" alt="License: MIT"></a>
12
+ </p>
13
+
14
+ ---
15
+
16
+ **Source Code**: [https://github.com/Alteian/strawberry-alchemy](https://github.com/Alteian/strawberry-alchemy)
17
+
18
+ ---
19
+
20
+ ## Features
21
+
22
+ | Module | What it does |
23
+ |---|---|
24
+ | **QueryOptimizer** | Analyzes Strawberry selection sets and builds a single optimized SQLAlchemy query — automatic `joinedload` / `selectinload`, column deferral, annotation injection |
25
+ | **FilterBuilder** | Translates Strawberry input types into SQLAlchemy `WHERE` clauses using a declarative operator system |
26
+ | **Repository** | Generic async CRUD with delete, dependent-map cascading, and lifecycle hooks |
27
+ | **Types** | Relay `Connection` / `Edge` / `PageInfo` pagination, `ListResult`, `BaseNodeType` |
28
+ | **Mapping** | Async helpers to convert SQLAlchemy instances → Strawberry types respecting the selected field tree |
29
+ | **Permissions** | Protocol-based permission primitives: `IsAuthenticated`, `RolePermission`, `OwnerPermission`, `ObjectAccessPermission`, plus resolver pattern and resource-bag |
30
+ | **Models** | Tiny SQLAlchemy `DeclarativeBase` with UUID primary key, timestamps, and automatic table naming |
31
+ | **Utilities** | `camel_to_snake`, `DateTimeProcessor`, `Ordering` enum, common exceptions |
32
+
33
+ ## Installation
34
+
35
+ ```bash
36
+ pip install strawberry-alchemy
37
+ # or with uv
38
+ uv add strawberry-alchemy
39
+ ```
40
+
41
+ ## Quick Start
42
+
43
+ ```python
44
+ import strawberry
45
+ from sqlalchemy.ext.asyncio import AsyncSession
46
+
47
+ from strawberry_alchemy import (
48
+ BaseNodeType,
49
+ BaseRepository,
50
+ FilterBuilder,
51
+ OptimizedListConnection,
52
+ QueryOptimizer,
53
+ )
54
+
55
+ # 1. Define your SQLAlchemy model (or use the provided Base)
56
+ from strawberry_alchemy.models import Base
57
+
58
+ # 2. Define your Strawberry type
59
+ @strawberry.type
60
+ class BookType(BaseNodeType):
61
+ title: str
62
+ author: str
63
+
64
+ # 3. Use QueryOptimizer in your resolver
65
+ @strawberry.type
66
+ class Query:
67
+ @strawberry.field
68
+ async def books(self, info: strawberry.Info) -> list[BookType]:
69
+ optimizer = QueryOptimizer(session=info.context.db, info=info)
70
+ result = await optimizer.optimize_query(model=Book)
71
+ return result.items
72
+ ```
73
+
74
+ ## Development
75
+
76
+ ```bash
77
+ git clone https://github.com/Alteian/strawberry-alchemy.git
78
+ cd strawberry-alchemy
79
+ uv sync
80
+
81
+ # Lint & test
82
+ uv run ruff check .
83
+ uv run pytest -v
84
+
85
+ # Build
86
+ uv build
87
+ ```
88
+
89
+ ## Contributing
90
+
91
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
92
+
93
+ ## License
94
+
95
+ [MIT](LICENSE)
@@ -0,0 +1,71 @@
1
+ [project]
2
+ name = "strawberry-alchemy"
3
+ version = "0.1.0"
4
+ description = "Toolkit for fast and easy strawberry+sqlalchemy apis"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ authors = [
9
+ { name = "David Roučka", email = "Alteian@proton.me" },
10
+ ]
11
+ keywords = ["graphql", "strawberry", "sqlalchemy", "async", "api", "pagination", "relay", "filtering", "permissions", "crud", "database"]
12
+ requires-python = ">=3.13"
13
+ classifiers = [
14
+ "Development Status :: 3 - Alpha",
15
+ "Framework :: AsyncIO",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Operating System :: OS Independent",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.13",
21
+ "Programming Language :: Python :: 3.14",
22
+ "Topic :: Software Development :: Libraries",
23
+ "Topic :: Software Development :: Libraries :: Python Modules",
24
+ "Typing :: Typed",
25
+ ]
26
+ dependencies = [
27
+ "strawberry-graphql>=0.220",
28
+ "sqlalchemy",
29
+ "pydantic>=2.0",
30
+ "python-dateutil>=2.7",
31
+ ]
32
+
33
+ [project.urls]
34
+ Homepage = "https://github.com/Alteian/strawberry-alchemy"
35
+ Repository = "https://github.com/Alteian/strawberry-alchemy"
36
+ Issues = "https://github.com/Alteian/strawberry-alchemy/issues"
37
+ Changelog = "https://github.com/Alteian/strawberry-alchemy/blob/master/CHANGELOG.md"
38
+
39
+ [dependency-groups]
40
+ dev = [
41
+ "pytest>=9.1",
42
+ "pytest-asyncio>=1.4",
43
+ "ruff>=0.15",
44
+ "mypy>=2.1",
45
+ "aiosqlite>=0.22",
46
+ "pre-commit>=4.6",
47
+ ]
48
+
49
+ [build-system]
50
+ requires = ["uv_build>=0.11.21"]
51
+ build-backend = "uv_build"
52
+
53
+ [tool.ruff]
54
+ target-version = "py313"
55
+ line-length = 120
56
+
57
+ [tool.ruff.lint]
58
+ select = ["E", "F", "W", "I", "N", "UP", "B", "A", "SIM", "TCH"]
59
+ ignore = ["E501"]
60
+
61
+ [tool.ruff.lint.per-file-ignores]
62
+ "src/strawberry_alchemy/repository/base.py" = ["A002"]
63
+
64
+ [tool.pytest.ini_options]
65
+ asyncio_mode = "auto"
66
+ testpaths = ["tests"]
67
+
68
+ [tool.mypy]
69
+ python_version = "3.13"
70
+ strict = true
71
+ plugins = ["strawberry.ext.mypy_plugin"]
@@ -0,0 +1,124 @@
1
+ from importlib.metadata import version as _version
2
+
3
+ __version__ = _version("strawberry-alchemy")
4
+
5
+ from strawberry_alchemy.enums import Ordering
6
+ from strawberry_alchemy.exceptions import NotFoundError
7
+ from strawberry_alchemy.filtering import (
8
+ AccessControlFilter,
9
+ BooleanFilter,
10
+ DateTimeFilter,
11
+ EnumFilter,
12
+ FilterBuilder,
13
+ FilterOperators,
14
+ IDFilter,
15
+ IntFilter,
16
+ StringFilter,
17
+ )
18
+ from strawberry_alchemy.mapping import (
19
+ map_sqlalchemy_list_to_types,
20
+ map_sqlalchemy_to_type,
21
+ )
22
+ from strawberry_alchemy.models import Base
23
+ from strawberry_alchemy.optimizer import (
24
+ AnnotateAncestry,
25
+ AnnotateAnyExists,
26
+ AnnotateCount,
27
+ AnnotateCustom,
28
+ AnnotateExists,
29
+ PrefetchRelated,
30
+ QueryAnalyzer,
31
+ QueryOptimizer,
32
+ QueryResult,
33
+ build_recursive_dependency_tree,
34
+ merge_dependency_trees,
35
+ normalize_dependency_fields,
36
+ optimize_field,
37
+ source_path_to_dependency_tree,
38
+ )
39
+ from strawberry_alchemy.permissions import (
40
+ BasePermissionResolver,
41
+ HasId,
42
+ IsAuthenticated,
43
+ ModelRegistryLike,
44
+ ObjectAccessPermission,
45
+ OwnerPermission,
46
+ PermissionContextLike,
47
+ ResourceInstances,
48
+ RolePermission,
49
+ UserLike,
50
+ extract_global_ids_from_info,
51
+ fetch_and_check_permissions,
52
+ map_ids_to_models,
53
+ )
54
+ from strawberry_alchemy.repository import (
55
+ BaseDeletionHandler,
56
+ BaseRepository,
57
+ )
58
+ from strawberry_alchemy.schema import BaseSchema
59
+ from strawberry_alchemy.types import (
60
+ BaseNodeType,
61
+ Edge,
62
+ ListResult,
63
+ OptimizedListConnection,
64
+ PageInfo,
65
+ SliceMetadata,
66
+ )
67
+ from strawberry_alchemy.utils import (
68
+ camel_to_snake,
69
+ )
70
+
71
+ __all__ = (
72
+ "__version__",
73
+ "AccessControlFilter",
74
+ "AnnotateAncestry",
75
+ "AnnotateAnyExists",
76
+ "AnnotateCount",
77
+ "AnnotateCustom",
78
+ "AnnotateExists",
79
+ "Base",
80
+ "BaseDeletionHandler",
81
+ "BaseNodeType",
82
+ "BasePermissionResolver",
83
+ "BaseRepository",
84
+ "BaseSchema",
85
+ "BooleanFilter",
86
+ "DateTimeFilter",
87
+ "Edge",
88
+ "EnumFilter",
89
+ "FilterBuilder",
90
+ "FilterOperators",
91
+ "HasId",
92
+ "IDFilter",
93
+ "IntFilter",
94
+ "IsAuthenticated",
95
+ "ListResult",
96
+ "ModelRegistryLike",
97
+ "NotFoundError",
98
+ "ObjectAccessPermission",
99
+ "OptimizedListConnection",
100
+ "Ordering",
101
+ "OwnerPermission",
102
+ "PageInfo",
103
+ "PermissionContextLike",
104
+ "PrefetchRelated",
105
+ "QueryAnalyzer",
106
+ "QueryOptimizer",
107
+ "QueryResult",
108
+ "ResourceInstances",
109
+ "RolePermission",
110
+ "SliceMetadata",
111
+ "StringFilter",
112
+ "UserLike",
113
+ "camel_to_snake",
114
+ "extract_global_ids_from_info",
115
+ "fetch_and_check_permissions",
116
+ "map_ids_to_models",
117
+ "map_sqlalchemy_list_to_types",
118
+ "map_sqlalchemy_to_type",
119
+ "optimize_field",
120
+ "build_recursive_dependency_tree",
121
+ "merge_dependency_trees",
122
+ "normalize_dependency_fields",
123
+ "source_path_to_dependency_tree",
124
+ )
@@ -0,0 +1,9 @@
1
+ from enum import Enum
2
+
3
+ import strawberry
4
+
5
+
6
+ @strawberry.enum
7
+ class Ordering(Enum):
8
+ ASC = "ASC"
9
+ DESC = "DESC"
@@ -0,0 +1,2 @@
1
+ class NotFoundError(Exception):
2
+ pass
@@ -0,0 +1,23 @@
1
+ from .access_control import AccessControlFilter
2
+ from .filter_builder import FilterBuilder
3
+ from .inputs import (
4
+ BooleanFilter,
5
+ DateTimeFilter,
6
+ EnumFilter,
7
+ IDFilter,
8
+ IntFilter,
9
+ StringFilter,
10
+ )
11
+ from .operators import FilterOperators
12
+
13
+ __all__ = (
14
+ "AccessControlFilter",
15
+ "BooleanFilter",
16
+ "DateTimeFilter",
17
+ "EnumFilter",
18
+ "FilterBuilder",
19
+ "FilterOperators",
20
+ "IDFilter",
21
+ "IntFilter",
22
+ "StringFilter",
23
+ )
@@ -0,0 +1,19 @@
1
+ from typing import Any
2
+
3
+
4
+ class AccessControlMeta(type):
5
+ def __init__(
6
+ cls: "type[AccessControlFilter]",
7
+ name: str,
8
+ bases: tuple[type, ...],
9
+ dct: dict[str, Any],
10
+ ) -> None:
11
+ if name != "AccessControlFilter" and not name.endswith("AccessFilter"):
12
+ raise TypeError(f"Subclass {name} must end with 'AccessFilter'")
13
+ super().__init__(name, bases, dct)
14
+
15
+
16
+ class AccessControlFilter(metaclass=AccessControlMeta):
17
+ @staticmethod
18
+ async def apply_filter(query: Any, model: type[Any], context_user: Any) -> Any:
19
+ raise NotImplementedError("Subclasses must implement the apply_filter method")