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.
- bootgraph-1.2.0/LICENSE +21 -0
- bootgraph-1.2.0/PKG-INFO +120 -0
- bootgraph-1.2.0/README.md +91 -0
- bootgraph-1.2.0/graphemy/__init__.py +31 -0
- bootgraph-1.2.0/graphemy/database/__init__.py +0 -0
- bootgraph-1.2.0/graphemy/database/operations.py +154 -0
- bootgraph-1.2.0/graphemy/database/utils.py +57 -0
- bootgraph-1.2.0/graphemy/dl.py +164 -0
- bootgraph-1.2.0/graphemy/models.py +95 -0
- bootgraph-1.2.0/graphemy/router.py +240 -0
- bootgraph-1.2.0/graphemy/schemas/__init__.py +0 -0
- bootgraph-1.2.0/graphemy/schemas/generators.py +868 -0
- bootgraph-1.2.0/graphemy/schemas/models.py +10 -0
- bootgraph-1.2.0/graphemy/setup.py +146 -0
- bootgraph-1.2.0/pyproject.toml +84 -0
bootgraph-1.2.0/LICENSE
ADDED
|
@@ -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.
|
bootgraph-1.2.0/PKG-INFO
ADDED
|
@@ -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
|
+
[](https://graphemy.readthedocs.io/en/latest/?badge=latest)
|
|
40
|
+
[](https://codecov.io/gh/MDoreto/graphemy)
|
|
41
|
+

|
|
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
|
+
[](https://graphemy.readthedocs.io/en/latest/?badge=latest)
|
|
12
|
+
[](https://codecov.io/gh/MDoreto/graphemy)
|
|
13
|
+

|
|
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))
|