bootgraph 1.2.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.
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023 Matheus Doreto
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
13
+ all 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
21
+ THE SOFTWARE.
@@ -0,0 +1,120 @@
1
+ Metadata-Version: 2.1
2
+ Name: bootgraph
3
+ Version: 1.2.0
4
+ Summary: A Python library for integrating SQLModel and Strawberry, providing a seamless GraphQL integration with FastAPI and advanced features for database interactions.
5
+ Home-page: https://github.com/MDoreto/graphemy
6
+ License: MIT
7
+ Author: Matheus Doreto
8
+ Author-email: matheusdoreto.md@gmail.com
9
+ Requires-Python: >=3.12,<4.0
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Framework :: FastAPI
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Natural Language :: English
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Database
19
+ Classifier: Topic :: Database :: Database Engines/Servers
20
+ Requires-Dist: black (>=24.8.0,<25.0.0)
21
+ Requires-Dist: fastapi (>=0.114.0,<0.115.0)
22
+ Requires-Dist: sqlmodel (>=0.0.22,<0.0.23)
23
+ Requires-Dist: strawberry-graphql[debug-server] (>=0.240.0,<0.241.0)
24
+ Project-URL: Bug Tracker, https://github.com/MDoreto/graphemy/issues
25
+ Project-URL: Documentation, https://graphemy.readthedocs.io/en/latest/
26
+ Project-URL: Repository, https://github.com/MDoreto/graphemy
27
+ Description-Content-Type: text/markdown
28
+
29
+
30
+ <p align="center">
31
+ <img src="./docs/assets/logo.png" />
32
+ </p>
33
+
34
+ # GRAPHEMY
35
+ <p align="center">
36
+ <em>Integrating SQLModel and Strawberry, providing a seamless GraphQL integration with Databases easy and fast.</em>
37
+ </p>
38
+
39
+ [![Documentation Status](https://readthedocs.org/projects/graphemy/badge/?version=latest)](https://graphemy.readthedocs.io/en/latest/?badge=latest)
40
+ [![codecov](https://codecov.io/gh/MDoreto/graphemy/graph/badge.svg?token=GJDMVBA425)](https://codecov.io/gh/MDoreto/graphemy)
41
+ ![CI](https://github.com/MDoreto/graphemy/actions/workflows/pipeline.yml/badge.svg)
42
+ <a href="https://pypi.org/project/graphemy" target="_blank">
43
+ <img src="https://img.shields.io/pypi/v/graphemy?color=%2334D058&label=pypi%20package" alt="Package version">
44
+ </a>
45
+
46
+
47
+ ---
48
+
49
+ **Documentation**: <a href="https://graphemy.readthedocs.io" target="_blank">https://graphemy.readthedocs.io</a>
50
+
51
+ **Source Code**: <a href="https://github.com/MDoreto/graphemy" target="_blank">https://github.com/MDoreto/graphemy</a>
52
+
53
+ ---
54
+
55
+
56
+ ## Overview
57
+
58
+ The Graphemy is designed to simplify and streamline the integration of SQLModel and Strawberry in Python projects. This library allows you to create a single class model, which, once declared, automatically provides GraphQL queries via Strawberry. These queries can be easily integrated into a FastAPI backend. All generated routes include filters on all fields, including a custom date filter. Additionally, it facilitates the creation of mutations for data modification and deletion by simply setting a variable in the model. The library also handles table relationships efficiently using Strawberry's dataloaders, providing a significant performance boost. Moreover, it offers a pre-configured authentication setup, which can be configured with just two functions.
59
+
60
+ ## Features
61
+
62
+ - Integration of SQLModel and Strawberry for GraphQL support.
63
+ - Automatic generation of GraphQL queries for FastAPI.
64
+ - Powerful filtering capabilities, including custom date filters.
65
+ - Effortless creation of mutations for data manipulation.
66
+ - Efficient handling of table relationships using Strawberry's dataloaders.
67
+ - Pre-configured authentication setup for easy configuration.
68
+
69
+ ## Prerequisites
70
+
71
+ Before you begin using Graphemy, it is highly recommended that you have some prior knowledge of the essential libraries upon which this project is built. This will help you make the most of the features and carry out integrations more effectively. Please make sure you are familiar with the following libraries:
72
+
73
+ **FastAPI**: A modern framework for building fast web APIs with Python. If you are not already familiar with FastAPI, you can refer to the [FastAPI documentation](https://fastapi.tiangolo.com/).
74
+
75
+ **SQLModel**: An object-relational mapping (ORM) library for Python that simplifies and streamlines database interactions. To learn more about SQLModel, visit the [SQLModel documentation](https://sqlmodel.tiangolo.com/).
76
+
77
+ **Strawberry**: A Python library for declaratively creating GraphQL schemas. For in-depth information on using Strawberry, access the [Strawberry documentation](https://strawberry.rocks/).
78
+
79
+ Having a solid understanding of these libraries is crucial to making the most of Graphemy and effortlessly creating GraphQL APIs.
80
+
81
+ ## Create a Project
82
+
83
+ I recomend you use Poetry, but you can use the enviroment manager that you want. So if you are using poetry, start the project:
84
+
85
+ ```bash
86
+ # Create poetry project
87
+ poetry new graphemy tutorial
88
+
89
+ # Start Environment
90
+ poetry shell
91
+ ```
92
+
93
+ You can also use the environment manager wanted, such as virtualenv
94
+
95
+ ```bash
96
+ # Create a directory for tutorial
97
+ mkdir graphemy-tutorial
98
+
99
+ # Enter into that directory
100
+ cd graphemy
101
+
102
+ # Create virtual environment
103
+ python -m venv venv
104
+
105
+ #Start Environment
106
+ venv/Scripts/Activate
107
+ ```
108
+
109
+ ## Requirements
110
+
111
+ Now install Graphemy :)
112
+ ```bash
113
+ poetry add graphemy
114
+ ```
115
+ Or using default python env with pip:
116
+
117
+ ```bash
118
+ pip install graphemy
119
+ ```
120
+
@@ -0,0 +1,91 @@
1
+
2
+ <p align="center">
3
+ <img src="./docs/assets/logo.png" />
4
+ </p>
5
+
6
+ # GRAPHEMY
7
+ <p align="center">
8
+ <em>Integrating SQLModel and Strawberry, providing a seamless GraphQL integration with Databases easy and fast.</em>
9
+ </p>
10
+
11
+ [![Documentation Status](https://readthedocs.org/projects/graphemy/badge/?version=latest)](https://graphemy.readthedocs.io/en/latest/?badge=latest)
12
+ [![codecov](https://codecov.io/gh/MDoreto/graphemy/graph/badge.svg?token=GJDMVBA425)](https://codecov.io/gh/MDoreto/graphemy)
13
+ ![CI](https://github.com/MDoreto/graphemy/actions/workflows/pipeline.yml/badge.svg)
14
+ <a href="https://pypi.org/project/graphemy" target="_blank">
15
+ <img src="https://img.shields.io/pypi/v/graphemy?color=%2334D058&label=pypi%20package" alt="Package version">
16
+ </a>
17
+
18
+
19
+ ---
20
+
21
+ **Documentation**: <a href="https://graphemy.readthedocs.io" target="_blank">https://graphemy.readthedocs.io</a>
22
+
23
+ **Source Code**: <a href="https://github.com/MDoreto/graphemy" target="_blank">https://github.com/MDoreto/graphemy</a>
24
+
25
+ ---
26
+
27
+
28
+ ## Overview
29
+
30
+ The Graphemy is designed to simplify and streamline the integration of SQLModel and Strawberry in Python projects. This library allows you to create a single class model, which, once declared, automatically provides GraphQL queries via Strawberry. These queries can be easily integrated into a FastAPI backend. All generated routes include filters on all fields, including a custom date filter. Additionally, it facilitates the creation of mutations for data modification and deletion by simply setting a variable in the model. The library also handles table relationships efficiently using Strawberry's dataloaders, providing a significant performance boost. Moreover, it offers a pre-configured authentication setup, which can be configured with just two functions.
31
+
32
+ ## Features
33
+
34
+ - Integration of SQLModel and Strawberry for GraphQL support.
35
+ - Automatic generation of GraphQL queries for FastAPI.
36
+ - Powerful filtering capabilities, including custom date filters.
37
+ - Effortless creation of mutations for data manipulation.
38
+ - Efficient handling of table relationships using Strawberry's dataloaders.
39
+ - Pre-configured authentication setup for easy configuration.
40
+
41
+ ## Prerequisites
42
+
43
+ Before you begin using Graphemy, it is highly recommended that you have some prior knowledge of the essential libraries upon which this project is built. This will help you make the most of the features and carry out integrations more effectively. Please make sure you are familiar with the following libraries:
44
+
45
+ **FastAPI**: A modern framework for building fast web APIs with Python. If you are not already familiar with FastAPI, you can refer to the [FastAPI documentation](https://fastapi.tiangolo.com/).
46
+
47
+ **SQLModel**: An object-relational mapping (ORM) library for Python that simplifies and streamlines database interactions. To learn more about SQLModel, visit the [SQLModel documentation](https://sqlmodel.tiangolo.com/).
48
+
49
+ **Strawberry**: A Python library for declaratively creating GraphQL schemas. For in-depth information on using Strawberry, access the [Strawberry documentation](https://strawberry.rocks/).
50
+
51
+ Having a solid understanding of these libraries is crucial to making the most of Graphemy and effortlessly creating GraphQL APIs.
52
+
53
+ ## Create a Project
54
+
55
+ I recomend you use Poetry, but you can use the enviroment manager that you want. So if you are using poetry, start the project:
56
+
57
+ ```bash
58
+ # Create poetry project
59
+ poetry new graphemy tutorial
60
+
61
+ # Start Environment
62
+ poetry shell
63
+ ```
64
+
65
+ You can also use the environment manager wanted, such as virtualenv
66
+
67
+ ```bash
68
+ # Create a directory for tutorial
69
+ mkdir graphemy-tutorial
70
+
71
+ # Enter into that directory
72
+ cd graphemy
73
+
74
+ # Create virtual environment
75
+ python -m venv venv
76
+
77
+ #Start Environment
78
+ venv/Scripts/Activate
79
+ ```
80
+
81
+ ## Requirements
82
+
83
+ Now install Graphemy :)
84
+ ```bash
85
+ poetry add graphemy
86
+ ```
87
+ Or using default python env with pip:
88
+
89
+ ```bash
90
+ pip install graphemy
91
+ ```
@@ -0,0 +1,31 @@
1
+ import os
2
+
3
+ from sqlmodel import Field
4
+
5
+ from graphemy.dl import Dl
6
+ from graphemy.models import Graphemy
7
+ from graphemy.router import GraphemyRouter
8
+ from graphemy.setup import Setup
9
+
10
+
11
+ def import_files(path):
12
+ """
13
+ Recursively imports all Python files found in the specified directory and its subdirectories,
14
+ excluding `__init__.py`. This function is intended to facilitate the dynamic loading of modules,
15
+ particularly useful in scenarios like automatic model discovery in web applications.
16
+
17
+ Args:
18
+ path (str): The directory path from which Python files should be imported.
19
+
20
+ Note:
21
+ - This function modifies the global namespace by dynamically importing modules.
22
+ - Import paths are adjusted to be relative, considering the package structure.
23
+ """
24
+ for root, dirs, files in os.walk(path):
25
+ for file in files:
26
+ if file.endswith(".py") and file != "__init__.py":
27
+ module_name = os.path.splitext(file)[0]
28
+ module_path = os.path.join(root, module_name)
29
+ module_path_rel = os.path.relpath(os.path.join(root, module_name))
30
+ module_path = module_path_rel.replace(os.path.sep, ".")
31
+ exec(f"import {module_path}")
File without changes
@@ -0,0 +1,154 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ from sqlalchemy.ext.asyncio import AsyncSession
4
+ from sqlalchemy.orm import sessionmaker
5
+ from sqlmodel import Session, and_, extract, or_, select
6
+
7
+ from ..schemas.models import DateFilter
8
+ from ..setup import Setup
9
+ from .utils import get_filter, get_keys
10
+
11
+ if TYPE_CHECKING:
12
+ from ..models import Graphemy
13
+
14
+
15
+ async def get_items(
16
+ model: "Graphemy",
17
+ parameters: list[tuple],
18
+ id: str | list[str] = "id",
19
+ many: bool = True,
20
+ ):
21
+ groups = {}
22
+ id_groups = {}
23
+ filters = {}
24
+ params = {}
25
+ for p in parameters:
26
+ f = p[0]
27
+ if not f in filters:
28
+ filters[f] = []
29
+ filters[f].append(p[1][1])
30
+ groups[p] = [] if many else None
31
+ if not p[1][1] in id_groups:
32
+ id_groups[p[1][1]] = []
33
+ id_groups[p[1][1]].append(p)
34
+ query = select(model)
35
+ query_filters = []
36
+ for i, f in enumerate(filters):
37
+ if f[1]:
38
+ filter_temp = []
39
+ for k, v in f[1]:
40
+ if v:
41
+ if isinstance(v[0], tuple):
42
+ if v[2][1]:
43
+ filter_temp.append(
44
+ extract("year", getattr(model, k)) == v[2][1]
45
+ )
46
+ if v[0][1]:
47
+ filter_temp.append(getattr(model, k).in_(list(v[0][1])))
48
+ if v[1][1] and v[1][1][0]:
49
+ filter_temp.append(getattr(model, k) >= v[1][1][0])
50
+ if v[1][1] and v[1][1][1]:
51
+ filter_temp.append(getattr(model, k) <= v[1][1][1])
52
+ else:
53
+ filter_temp.append(getattr(model, k).in_(v))
54
+ else:
55
+ filter_temp = [True]
56
+
57
+ query_filters.append(
58
+ and_(*filter_temp, get_filter(model, filters[f], id, params, i))
59
+ )
60
+ results = await Setup.execute_query(
61
+ query.where(or_(*query_filters)).params(**params), model.__enginename__
62
+ )
63
+ for r in results:
64
+ temp = id_groups[get_keys(r, id)]
65
+
66
+ if len(temp) == 1:
67
+ key = temp[0]
68
+ if many:
69
+ groups[key].append(r)
70
+ else:
71
+ groups[key] = r
72
+ else:
73
+ for t in temp:
74
+ if not t[0][1] or all([getattr(r, k) in v for k, v in t[0][1] if v]):
75
+ if many:
76
+ groups[t].append(r)
77
+ else:
78
+ groups[t] = r
79
+ return groups.values()
80
+
81
+
82
+ async def get_all(model: "Graphemy", filters, query_filter) -> list:
83
+ query = select(model).where(query_filter)
84
+ if filters:
85
+ filters = vars(filters)
86
+ for k, v in filters.items():
87
+ if isinstance(v, DateFilter):
88
+ if v.year:
89
+ query = query.where(extract("year", getattr(model, k)) == v.year)
90
+ if v.items:
91
+ query = query.where(getattr(model, k).in_(v.items))
92
+ if v.range and v.range[0]:
93
+ query = query.where(getattr(model, k) >= (v.range[0]))
94
+ if v.range and v.range[1]:
95
+ query = query.where(getattr(model, k) <= (v.range[1]))
96
+ elif filters[k]:
97
+ query = query.where(getattr(model, k).in_(filters[k]))
98
+ r = await Setup.execute_query(query, model.__enginename__)
99
+ return r
100
+
101
+
102
+ async def put_item(model: "Graphemy", item, id="id"):
103
+ id = [getattr(item, i) for i in id]
104
+ kwargs = vars(item)
105
+ engine = Setup.engine[model.__enginename__]
106
+ if Setup.async_engine:
107
+ async_session = sessionmaker(
108
+ engine, class_=AsyncSession, expire_on_commit=False
109
+ )
110
+ async with async_session() as session:
111
+ if not id or None in id:
112
+ new_item = model(**kwargs)
113
+ else:
114
+ new_item = await session.get(model, id)
115
+ if not new_item:
116
+ new_item = model(**kwargs)
117
+ for key, value in kwargs.items():
118
+ setattr(new_item, key, value)
119
+ session.add(new_item)
120
+ await session.commit()
121
+ await session.refresh(new_item)
122
+ else:
123
+ with Session(engine) as session:
124
+ if not id or None in id:
125
+ new_item = model(**kwargs)
126
+ else:
127
+ new_item = session.get(model, id)
128
+ if not new_item:
129
+ new_item = model(**kwargs)
130
+ for key, value in kwargs.items():
131
+ setattr(new_item, key, value)
132
+ session.add(new_item)
133
+ session.commit()
134
+ session.refresh(new_item)
135
+ return new_item
136
+
137
+
138
+ async def delete_item(model: "Graphemy", item, id="id"):
139
+ id = [getattr(item, i) for i in id]
140
+ engine = Setup.engine[model.__enginename__]
141
+ if Setup.async_engine:
142
+ async_session = sessionmaker(
143
+ engine, class_=AsyncSession, expire_on_commit=False
144
+ )
145
+ async with async_session() as session:
146
+ item = await session.get(model, id)
147
+ await session.delete(item)
148
+ await session.commit()
149
+ else:
150
+ with Session(engine) as session:
151
+ item = session.get(model, id)
152
+ session.delete(item)
153
+ session.commit()
154
+ return item
@@ -0,0 +1,57 @@
1
+ from datetime import date
2
+ from typing import TYPE_CHECKING
3
+
4
+ from sqlalchemy.sql.elements import BinaryExpression
5
+ from sqlmodel import and_, bindparam, or_
6
+
7
+ if TYPE_CHECKING:
8
+ from ..models import Graphemy
9
+
10
+
11
+ def get_keys(model: "Graphemy", id: str | list[str]) -> tuple | str:
12
+ if isinstance(id, list):
13
+ return tuple([getattr(model, id[i]) for i in range(len(id))])
14
+ value = getattr(model, id)
15
+ if isinstance(value, str):
16
+ value = value.strip()
17
+ return value
18
+
19
+
20
+ def get_filter(
21
+ model: "Graphemy",
22
+ keys: tuple,
23
+ id: str | list[str],
24
+ params: dict,
25
+ i: int,
26
+ ) -> BinaryExpression:
27
+ if isinstance(id, list):
28
+ filter = []
29
+ for j, key in enumerate(keys):
30
+ f = []
31
+ if None not in key:
32
+ for k in range(len(id)):
33
+ f.append(
34
+ id[k]
35
+ if type(id[k]) == int
36
+ else (
37
+ id[k][1:]
38
+ if id[k].startswith("_")
39
+ else getattr(model, id[k])
40
+ == bindparam(
41
+ f"p{i}_{j}_{k}",
42
+ literal_execute=not isinstance(key[k], date),
43
+ )
44
+ )
45
+ )
46
+ params[f"p{i}_{j}_{k}"] = key[k]
47
+ filter.append(and_(*f))
48
+ else:
49
+ filter.append(False)
50
+ return or_(*filter)
51
+ elif None in keys:
52
+ keys.remove(None)
53
+ params[f"p{i}"] = keys
54
+ a = getattr(model, id).in_(bindparam(f"p{i}", expanding=True, literal_execute=True))
55
+ return getattr(model, id).in_(
56
+ bindparam(f"p{i}", expanding=True, literal_execute=True)
57
+ )
@@ -0,0 +1,164 @@
1
+ from typing import Type
2
+
3
+ from strawberry.dataloader import DataLoader
4
+
5
+ # from . import Graphemy
6
+ from .schemas.models import DateFilter
7
+
8
+
9
+ class CustomResolver:
10
+ def __init__(self, resolver_func):
11
+ self.resolver_func = resolver_func
12
+
13
+ class ManyRelation:
14
+ def __init__(
15
+ self,
16
+ response_class: Type["Graphemy"],
17
+ relation_class: Type["Graphemy"],
18
+ source: str | list[str],
19
+ source_relation: str | list[str],
20
+ target: str | list[str],
21
+ target_relation: str | list[str],
22
+ foreign_key: bool = None,
23
+ ):
24
+ if type(source) != type(source_relation):
25
+ raise "source and source_relation must have same type"
26
+ if type(target) != type(target_relation):
27
+ raise "target and target_relation must have same type"
28
+ if type(source) == list:
29
+ if len(source) != len(source_relation):
30
+ raise "source and source_relation must have same length"
31
+ ids = {}
32
+ for i, id in enumerate(source_relation):
33
+ ids[id] = source[i]
34
+ source_relation.sort()
35
+ source = [ids[id] for id in source_relation]
36
+ if type(target) == list:
37
+ if len(target) != len(target_relation):
38
+ raise "target and target_relation must have same length"
39
+ ids = {}
40
+ for i, id in enumerate(target_relation):
41
+ ids[id] = source[i]
42
+ target_relation.sort()
43
+ target = [ids[id] for id in target_relation]
44
+ self.source = source
45
+ self.source_relation = source_relation
46
+ self.foreign_key = foreign_key
47
+ self.response_class = response_class
48
+ self.relation_class = relation_class
49
+ self.target = target
50
+ self.target_relation = target_relation
51
+ # There might be checks that relation_class has target and response_class has source
52
+
53
+
54
+ class Dl:
55
+ """
56
+ A utility class designed to facilitate the linking of source and target fields
57
+ across different models. This is particularly useful for setting up data loaders
58
+ where fields from one model may depend on fields in another, and if foreign keys should be created based on relationships.
59
+
60
+ Attributes:
61
+ source (str | list[str]): The source field(s) from where data is to be fetched.
62
+ target (str | list[str]): The target field(s) where data is to be deposited.
63
+ foreign_key (bool): Indicates whether the relationship should create a foreign key
64
+ (default is False).
65
+
66
+ Raises:
67
+ ValueError: If the types of source and target do not match, or if they are lists
68
+ and do not have the same length.
69
+ """
70
+
71
+ source: str | list[str]
72
+ target: str | list[str]
73
+ foreign_key: bool | None = None
74
+
75
+ def __init__(
76
+ self,
77
+ source: str | list[str],
78
+ target: str | list[str],
79
+ foreign_key: bool = None,
80
+ ):
81
+ if type(source) != type(target):
82
+ raise "source and target must have same type"
83
+ if type(source) == list:
84
+ if len(source) != len(target):
85
+ raise "source and target must have same length"
86
+ ids = {}
87
+ for i, id in enumerate(target):
88
+ ids[id] = source[i]
89
+ target.sort()
90
+ source = [ids[id] for id in target]
91
+ self.source = source
92
+ self.target = target
93
+ self.foreign_key = foreign_key
94
+
95
+
96
+ class GraphemyDataLoader(DataLoader):
97
+ """
98
+ A customized DataLoader that handles additional filtering mechanisms during data
99
+ retrieval processes. It is capable of using predefined filter methods to process data
100
+ based on request-specific parameters.
101
+
102
+ Attributes:
103
+ filter_method (callable): A method that applies additional filtering to the data
104
+ based on the request context and specified filters.
105
+ context: The context of the request, used to pass additional parameters to the
106
+ filter_method.
107
+
108
+ Methods:
109
+ load: Overridden to apply filters before returning the data, enhancing the
110
+ DataLoader's functionality to cater to complex querying needs.
111
+ """
112
+
113
+ def __init__(self, filter_method=None, context: dict = None, **kwargs):
114
+ self.filter_method = filter_method
115
+ self.context = context
116
+ super().__init__(**kwargs)
117
+
118
+ async def load(self, keys, filters: dict | None):
119
+ filters["keys"] = (
120
+ tuple(keys)
121
+ if isinstance(keys, list)
122
+ else keys.strip() if isinstance(keys, str) else keys
123
+ )
124
+ data = await super().load(dict_to_tuple(filters))
125
+ if self.filter_method:
126
+ data = self.filter_method(data, self.context)
127
+ return data
128
+
129
+
130
+ def dict_to_tuple(data: dict) -> tuple:
131
+ """
132
+ Converts a dictionary into a tuple, recursively processing nested dictionaries
133
+ and lists to ensure they are in a hashable and comparable format. This is essential
134
+ for caching mechanisms within DataLoaders.
135
+
136
+ Args:
137
+ data (dict): The dictionary to be converted.
138
+
139
+ Returns:
140
+ tuple: A tuple representation of the original dictionary, suitable for use as
141
+ a key in caching operations.
142
+ """
143
+ result = []
144
+ for key, value in data.items():
145
+ if isinstance(value, DateFilter):
146
+ value = vars(value)
147
+ if isinstance(value, dict):
148
+ nested_tuples = dict_to_tuple(value)
149
+ result.append((key, nested_tuples))
150
+ elif isinstance(value, list):
151
+ nested_tuples = tuple(
152
+ sorted(
153
+ (
154
+ dict_to_tuple(item)
155
+ if isinstance(item, dict) or isinstance(item, DateFilter)
156
+ else item
157
+ )
158
+ for item in value
159
+ )
160
+ )
161
+ result.append((key, nested_tuples))
162
+ else:
163
+ result.append((key, value))
164
+ return tuple(sorted(result))