fquery 0.2__tar.gz → 0.3__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 (39) hide show
  1. {fquery-0.2 → fquery-0.3}/PKG-INFO +1 -1
  2. fquery-0.3/fquery/sqlmodel.py +280 -0
  3. {fquery-0.2 → fquery-0.3}/fquery.egg-info/PKG-INFO +1 -1
  4. fquery-0.3/fquery.egg-info/requires.txt +10 -0
  5. {fquery-0.2 → fquery-0.3}/setup.py +7 -2
  6. {fquery-0.2 → fquery-0.3}/tests/benchmark.py +28 -2
  7. {fquery-0.2 → fquery-0.3}/tests/test_sqlmodel.py +32 -10
  8. fquery-0.2/fquery/sqlmodel.py +0 -96
  9. fquery-0.2/fquery.egg-info/requires.txt +0 -9
  10. {fquery-0.2 → fquery-0.3}/LICENSE +0 -0
  11. {fquery-0.2 → fquery-0.3}/README.md +0 -0
  12. {fquery-0.2 → fquery-0.3}/fquery/__init__.py +0 -0
  13. {fquery-0.2 → fquery-0.3}/fquery/aitertools.py +0 -0
  14. {fquery-0.2 → fquery-0.3}/fquery/async_utils.py +0 -0
  15. {fquery-0.2 → fquery-0.3}/fquery/django.py +0 -0
  16. {fquery-0.2 → fquery-0.3}/fquery/execute.py +0 -0
  17. {fquery-0.2 → fquery-0.3}/fquery/fgraphql.py +0 -0
  18. {fquery-0.2 → fquery-0.3}/fquery/malloy_builder.py +0 -0
  19. {fquery-0.2 → fquery-0.3}/fquery/query.py +0 -0
  20. {fquery-0.2 → fquery-0.3}/fquery/resolve.py +0 -0
  21. {fquery-0.2 → fquery-0.3}/fquery/sql_builder.py +0 -0
  22. {fquery-0.2 → fquery-0.3}/fquery/view_model.py +0 -0
  23. {fquery-0.2 → fquery-0.3}/fquery/visitor.py +0 -0
  24. {fquery-0.2 → fquery-0.3}/fquery/walk.py +0 -0
  25. {fquery-0.2 → fquery-0.3}/fquery.egg-info/SOURCES.txt +0 -0
  26. {fquery-0.2 → fquery-0.3}/fquery.egg-info/dependency_links.txt +0 -0
  27. {fquery-0.2 → fquery-0.3}/fquery.egg-info/top_level.txt +0 -0
  28. {fquery-0.2 → fquery-0.3}/pyproject.toml +0 -0
  29. {fquery-0.2 → fquery-0.3}/setup.cfg +0 -0
  30. {fquery-0.2 → fquery-0.3}/tests/__init__.py +0 -0
  31. {fquery-0.2 → fquery-0.3}/tests/async_test.py +0 -0
  32. {fquery-0.2 → fquery-0.3}/tests/django_example_model.py +0 -0
  33. {fquery-0.2 → fquery-0.3}/tests/graphql_mock_user.py +0 -0
  34. {fquery-0.2 → fquery-0.3}/tests/mock_user.py +0 -0
  35. {fquery-0.2 → fquery-0.3}/tests/test_malloy.py +0 -0
  36. {fquery-0.2 → fquery-0.3}/tests/test_operators.py +0 -0
  37. {fquery-0.2 → fquery-0.3}/tests/test_sql.py +0 -0
  38. {fquery-0.2 → fquery-0.3}/tests/test_walk.py +0 -0
  39. {fquery-0.2 → fquery-0.3}/tests/test_walk_obj.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fquery
3
- Version: 0.2
3
+ Version: 0.3
4
4
  Summary: A graph query engine
5
5
  Home-page: https://github.com/adsharma/fquery
6
6
  Classifier: Programming Language :: Python :: 3
@@ -0,0 +1,280 @@
1
+ import dataclasses
2
+ from dataclasses import _FIELD, dataclass, field, fields, is_dataclass
3
+ from datetime import date, datetime, time
4
+ from typing import (
5
+ ClassVar,
6
+ ForwardRef,
7
+ List,
8
+ Optional,
9
+ Union,
10
+ get_args,
11
+ get_origin,
12
+ get_type_hints,
13
+ )
14
+
15
+ import inflection
16
+ from sqlalchemy import (
17
+ Boolean,
18
+ Date,
19
+ DateTime,
20
+ Float,
21
+ Integer,
22
+ LargeBinary,
23
+ Sequence,
24
+ String,
25
+ Time,
26
+ )
27
+ from sqlalchemy.orm.base import Mapped
28
+ from sqlmodel import Column, Field, Relationship, SQLModel
29
+
30
+ SA_TYPEMAP = {
31
+ int: Integer,
32
+ int | None: Integer,
33
+ float: Float,
34
+ str: String,
35
+ bool: Boolean,
36
+ datetime: DateTime,
37
+ date: Date,
38
+ time: Time,
39
+ bytes: LargeBinary, # or Binary for smaller data
40
+ }
41
+
42
+ GLOBAL_ID_SEQ = Sequence("global_id_seq") # define sequence explicitly
43
+ SQL_PK = {"metadata": {"SQL": {"primary_key": True}}}
44
+
45
+
46
+ def unique():
47
+ return field(default=None, metadata={"SQL": {"unique": True}})
48
+
49
+
50
+ def foreign_key(name):
51
+ return field(
52
+ default=None,
53
+ metadata={
54
+ "SQL": {"relationship": True, "back_populates": False, "fk_name": name}
55
+ },
56
+ )
57
+
58
+
59
+ def one_to_many():
60
+ return field(default=None, metadata={"SQL": {"relationship": True}})
61
+
62
+
63
+ def many_to_one(key_column=None, back_populates=None):
64
+ ret = field(
65
+ default=None, metadata={"SQL": {"relationship": True, "many_to_one": True}}
66
+ )
67
+ # if key_column is None, we default to {key_table_name}.id
68
+ if key_column is not None:
69
+ ret.metadata["SQL"]["key_column"] = key_column
70
+ if back_populates is not None:
71
+ ret.metadata["SQL"]["back_populates"] = back_populates
72
+ return ret
73
+
74
+
75
+ def sqlmodel(cls):
76
+ return model()(dataclass(kw_only=True)(cls))
77
+
78
+
79
+ def model(table: bool = True, table_name: str = None, global_id: bool = False):
80
+ """
81
+ A decorator that generates a SQLModel from a dataclass.
82
+
83
+ Args:
84
+ table_name (str): The name of the database table. Defaults to the name of the dataclass.
85
+
86
+ Returns:
87
+ A decorator that generates a SQLModel from a dataclass.
88
+ """
89
+
90
+ def sqlmodel(self) -> SQLModel:
91
+ attrs = {name: getattr(self, name) for name in self.__sqlmodel__.__fields__}
92
+ return self.__sqlmodel__(**attrs)
93
+
94
+ def get_field_def(cls, field) -> Union[Field, Relationship]:
95
+ sql_meta = field.metadata.get("SQL", {})
96
+ has_foreign_key = bool(sql_meta.get("foreign_key", None))
97
+ has_relationship = bool(sql_meta.get("relationship", None))
98
+ has_unique_constraint = sql_meta.get("unique", False)
99
+ if has_unique_constraint:
100
+ return Field(unique=True)
101
+
102
+ if not sql_meta or not (has_foreign_key or has_relationship):
103
+ sql_default_factory = field.default_factory
104
+ if isinstance(sql_default_factory, dataclasses._MISSING_TYPE):
105
+ sql_default_factory = None
106
+ return Field(
107
+ default_factory=sql_default_factory,
108
+ # TODO: revisit the idea of using string for unknown types
109
+ sa_column=Column(
110
+ SA_TYPEMAP.get(field.type, String),
111
+ GLOBAL_ID_SEQ if global_id else cls.id_seq,
112
+ primary_key=(
113
+ field.name == "id"
114
+ or field.metadata.get("SQL", {}).get("primary_key", False)
115
+ ),
116
+ ),
117
+ )
118
+ if has_relationship:
119
+ back_populates = sql_meta.get("back_populates", None)
120
+ if back_populates is False:
121
+ return Relationship()
122
+ if not back_populates:
123
+ back_populates = inflection.underscore(cls.__name__)
124
+ if sql_meta.get("many_to_one", False):
125
+ back_populates = inflection.pluralize(back_populates)
126
+ return Relationship(back_populates=back_populates)
127
+ if has_foreign_key:
128
+ return Field(default=None, foreign_key=sql_meta["foreign_key"])
129
+ raise "Unsupported case"
130
+
131
+ def get_field_type(field, cls):
132
+ sql_meta = field.metadata.get("SQL", {})
133
+ has_foreign_key = bool(sql_meta.get("foreign_key", None))
134
+ has_relationship = bool(sql_meta.get("relationship", None))
135
+ has_many_to_one_relationship = bool(sql_meta.get("many_to_one", None))
136
+ if has_foreign_key:
137
+ # Translate ClassName to id type.
138
+ # TODO: what if the id type is different?
139
+ return Optional[int]
140
+ if has_relationship:
141
+ type_class = field.type
142
+ other_class = type_class.__args__[0]
143
+ if has_many_to_one_relationship:
144
+ try:
145
+ type_class = get_type_hints(cls)[field.name]
146
+ except NameError:
147
+ # TODO: log exception?
148
+ pass
149
+ else:
150
+ return Optional[other_class.__sqlmodel__]
151
+ return field.type
152
+
153
+ def patch_back_populates_types(field, back_populates, cls, sqlmodel_cls):
154
+ sql_meta = field.metadata.get("SQL", {})
155
+ has_relationship = bool(sql_meta.get("relationship", None))
156
+ has_many_to_one_relationship = bool(sql_meta.get("many_to_one", None))
157
+ if has_relationship:
158
+ if has_many_to_one_relationship:
159
+ type_class = field.type
160
+ try:
161
+ type_class = get_type_hints(cls)[field.name]
162
+ except NameError:
163
+ # TODO: log exception?
164
+ pass
165
+ inner = type_class.__args__[0]
166
+ if isinstance(inner, ForwardRef):
167
+ # can't patch right now. Try at a later time via back_populates
168
+ return
169
+ other_class = inner.__sqlmodel__
170
+ old = other_class.__annotations__[back_populates]
171
+ # Should be sqlalchemy.orm.base.Mapped[typing.List[ForwardRef('T')]]
172
+ # replace it with Mapped[List[sqlmodel_cls]]
173
+ origin = get_origin(old)
174
+ inner = get_args(old)
175
+ if origin == Mapped and len(inner) and get_origin(inner[0]) is list:
176
+ other_class.__annotations__[back_populates] = Mapped[
177
+ List[sqlmodel_cls]
178
+ ]
179
+ other_class.sqlmodel_rebuild()
180
+
181
+ # Replace Optional['T'] with Optional[TSQLModel]
182
+ old = field.type
183
+ origin = get_origin(old)
184
+ inner = get_args(old)
185
+ needs_rebuild = False
186
+ if origin == Union and len(inner) and inner[0] == ForwardRef(cls.__name__):
187
+ sqlmodel_cls.__annotations__[field.name] = Optional[sqlmodel_cls]
188
+ needs_rebuild = True
189
+
190
+ # Replace Optional[T] with Optional[TSQLModel] if T is a dataclass
191
+ if origin == Union and len(inner) and is_dataclass(inner[0]):
192
+ sqlmodel_cls.__annotations__[field.name] = Optional[inner[0].__sqlmodel__]
193
+ needs_rebuild = True
194
+
195
+ if needs_rebuild:
196
+ sqlmodel_cls.sqlmodel_rebuild()
197
+
198
+ def default_table_name(clsname: str) -> str:
199
+ return inflection.underscore(inflection.pluralize(clsname))
200
+
201
+ def decorator(cls):
202
+ # Check if the class is a dataclass
203
+ if not is_dataclass(cls):
204
+ raise ValueError("The class must be a dataclass")
205
+
206
+ nonlocal table_name
207
+ table_name = table_name or default_table_name(cls.__name__)
208
+
209
+ if not global_id:
210
+ cls.id_seq = Sequence(f"{table_name}_seq")
211
+
212
+ # Insert any foreign keys as necessary
213
+ for cfield in fields(cls):
214
+ sql_meta = cfield.metadata.get("SQL", {})
215
+ has_relationship = bool(sql_meta.get("relationship", None))
216
+ if has_relationship:
217
+ many_to_one = sql_meta.get("many_to_one", False)
218
+ foreign_key_name = cfield.name + "_id"
219
+ key_table_name = table_name
220
+ key_column_name = sql_meta.get("fk_name", f"{key_table_name}.id")
221
+ if many_to_one:
222
+ type_class = cfield.type
223
+ other_class = type_class.__args__[0]
224
+ if isinstance(other_class, ForwardRef):
225
+ other_class = other_class.__forward_arg__
226
+ other_class = getattr(other_class, "__name__", other_class)
227
+ key_table_name = default_table_name(other_class)
228
+ key_column_name = sql_meta.get(
229
+ "key_column_name", f"{key_table_name}.id"
230
+ )
231
+ back_populates = sql_meta.get("back_populates", None)
232
+ if back_populates is False or many_to_one:
233
+
234
+ new_field = field(
235
+ default=None,
236
+ metadata={"SQL": {"foreign_key": key_column_name}},
237
+ )
238
+ new_field._field_type = _FIELD
239
+ new_field.name = foreign_key_name
240
+ new_field.type = Optional[int]
241
+ cls.__dataclass_fields__[foreign_key_name] = new_field
242
+ setattr(cls, new_field.name, new_field.default)
243
+
244
+ # Generate the SQLModel class
245
+ sqlmodel_cls = type(
246
+ cls.__name__ + "SQLModel",
247
+ (SQLModel,),
248
+ {
249
+ # Table name is a plural and hence the 's' at the end
250
+ "__tablename__": table_name,
251
+ # Add type annotations to the generated fields
252
+ "__annotations__": {
253
+ **{field.name: get_field_type(field, cls) for field in fields(cls)},
254
+ **{
255
+ "Config": ClassVar,
256
+ },
257
+ },
258
+ # pydantic wants this
259
+ "__module__": cls.__module__,
260
+ "Config": {"exclude": {"__sqlmodel__", "sqlmodel"}},
261
+ **{field.name: get_field_def(cls, field) for field in fields(cls)},
262
+ },
263
+ # For SQLModel's SQLModelMetaClass
264
+ table=table,
265
+ )
266
+ cls.__sqlmodel__ = sqlmodel_cls
267
+ # Update type annotations in any class with a relationship with this class to point
268
+ # to the SQLModel, not the dataclass
269
+ for cfield in fields(cls):
270
+ if not cfield.name in sqlmodel_cls.__sqlmodel_relationships__:
271
+ continue
272
+ rel = sqlmodel_cls.__sqlmodel_relationships__.get(cfield.name, None)
273
+ if rel and hasattr(rel, "back_populates"):
274
+ patch_back_populates_types(
275
+ cfield, rel.back_populates, cls, sqlmodel_cls
276
+ )
277
+ cls.sqlmodel = sqlmodel
278
+ return cls
279
+
280
+ return decorator
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fquery
3
- Version: 0.2
3
+ Version: 0.3
4
4
  Summary: A graph query engine
5
5
  Home-page: https://github.com/adsharma/fquery
6
6
  Classifier: Programming Language :: Python :: 3
@@ -0,0 +1,10 @@
1
+ aioitertools
2
+
3
+ [SQL]
4
+ duckdb_engine>=0.14.0
5
+ inflection>=0.5.1
6
+ pypika>=0.36.5
7
+ sqlmodel@ git+https://github.com/adsharma/sqlmodel.git@sqlmodel_rebuild
8
+
9
+ [graphql]
10
+ strawberry>=0.37.1
@@ -11,7 +11,7 @@ with open("README.md", "r") as fh:
11
11
 
12
12
  setuptools.setup(
13
13
  name="fquery",
14
- version="0.2",
14
+ version="0.3",
15
15
  description="A graph query engine",
16
16
  url="https://github.com/adsharma/fquery",
17
17
  long_description=long_description,
@@ -27,7 +27,12 @@ setuptools.setup(
27
27
  install_requires=["aioitertools"],
28
28
  test_requires=["sqlalchemy >= 2.0.36"],
29
29
  extras_require={
30
- "SQL": ["pypika >= 0.36.5", "sqlmodel >= 0.0.22", "duckdb_engine >= 0.14.0"],
30
+ "SQL": [
31
+ "pypika >= 0.36.5",
32
+ "sqlmodel@git+https://github.com/adsharma/sqlmodel.git@sqlmodel_rebuild",
33
+ "duckdb_engine >= 0.14.0",
34
+ "inflection >= 0.5.1",
35
+ ],
31
36
  "graphql": ["strawberry >= 0.37.1"],
32
37
  },
33
38
  )
@@ -2,6 +2,8 @@ import timeit
2
2
  from dataclasses import dataclass
3
3
  from datetime import datetime
4
4
 
5
+ import pydantic
6
+
5
7
  from fquery.sqlmodel import model
6
8
 
7
9
 
@@ -11,8 +13,17 @@ class User:
11
13
  id: int
12
14
  name: str
13
15
  email: str
14
- created_at: datetime = None
15
- updated_at: datetime = None
16
+ created_at: datetime | None = None
17
+ updated_at: datetime | None = None
18
+
19
+
20
+ @pydantic.dataclasses.dataclass
21
+ class UserPydantic:
22
+ id: int
23
+ name: str
24
+ email: str
25
+ created_at: datetime | None = None
26
+ updated_at: datetime | None = None
16
27
 
17
28
 
18
29
  def create_user():
@@ -23,6 +34,10 @@ def create_user_sqlmodel():
23
34
  return User(1, "John Doe", "john@example.com").sqlmodel()
24
35
 
25
36
 
37
+ def create_user_pydantic():
38
+ return UserPydantic(1, "John Doe", "john@example.com")
39
+
40
+
26
41
  # Run the benchmark
27
42
  num_iterations = 100000
28
43
  time_taken = timeit.timeit(create_user, number=num_iterations)
@@ -40,3 +55,14 @@ print(f"Creating {num_iterations} User SQL Model objects took {time_taken:.6f} s
40
55
  print(
41
56
  f"Average time per object creation: {(time_taken / num_iterations) * 1e9:.2f} nanoseconds"
42
57
  )
58
+
59
+ # Run the benchmark
60
+ num_iterations = 100000
61
+ time_taken = timeit.timeit(create_user_pydantic, number=num_iterations)
62
+
63
+ print(
64
+ f"Creating {num_iterations} User Pydantic Model objects took {time_taken:.6f} seconds"
65
+ )
66
+ print(
67
+ f"Average time per object creation: {(time_taken / num_iterations) * 1e9:.2f} nanoseconds"
68
+ )
@@ -1,25 +1,41 @@
1
- from dataclasses import dataclass, field
1
+ from dataclasses import field
2
2
  from datetime import datetime
3
+ from typing import List, Optional
3
4
 
4
5
  from sqlalchemy import create_engine
5
6
  from sqlalchemy.orm import sessionmaker
6
7
  from sqlmodel import SQLModel
7
8
 
8
- from fquery.sqlmodel import SQL_PK, model
9
+ from fquery.sqlmodel import (
10
+ SQL_PK,
11
+ foreign_key,
12
+ many_to_one,
13
+ one_to_many,
14
+ sqlmodel,
15
+ unique,
16
+ )
9
17
 
10
18
 
11
- @model(global_id=True)
12
- @dataclass
19
+ @sqlmodel
13
20
  class User:
14
- name: str
15
- email: str
16
21
  id: int | None = None
22
+ name: str
23
+ email: str = unique()
17
24
  created_at: datetime = None
18
25
  updated_at: datetime = None
19
26
 
27
+ friend: Optional["User"] = foreign_key("users.id")
28
+ reviews: List["Review"] = one_to_many()
29
+
20
30
 
21
- @model(global_id=True)
22
- @dataclass
31
+ @sqlmodel
32
+ class Review:
33
+ id: int | None = None
34
+ score: int
35
+ user: Optional[User] = many_to_one("users.id")
36
+
37
+
38
+ @sqlmodel
23
39
  class Relation:
24
40
  src: int | None = field(**SQL_PK)
25
41
  type: int = field(**SQL_PK)
@@ -41,9 +57,9 @@ user1 = User(
41
57
  email="jane@example.com",
42
58
  created_at=datetime.now(),
43
59
  updated_at=datetime.now(),
60
+ friend=user.id,
44
61
  )
45
62
 
46
-
47
63
  # The following is equivalent to: user.sql_model()
48
64
  # from sqlmodel import Field
49
65
  # from typing import Optional
@@ -70,7 +86,13 @@ def test_sqlmodel():
70
86
  session.add(user1.sqlmodel())
71
87
  session.commit()
72
88
 
73
- relation = Relation(user.id, 1, user1.id, datetime.now(), datetime.now())
89
+ relation = Relation(
90
+ src=user.id,
91
+ type=1,
92
+ dst=user1.id,
93
+ created_at=datetime.now(),
94
+ updated_at=datetime.now(),
95
+ )
74
96
  session.add(relation.sqlmodel())
75
97
  session.commit()
76
98
  # Read all users from the database
@@ -1,96 +0,0 @@
1
- from dataclasses import fields, is_dataclass
2
- from datetime import date, datetime, time
3
- from typing import ClassVar
4
-
5
- from sqlalchemy import (
6
- Boolean,
7
- Date,
8
- DateTime,
9
- Float,
10
- Integer,
11
- LargeBinary,
12
- Sequence,
13
- String,
14
- Time,
15
- )
16
- from sqlmodel import Column, Field, SQLModel
17
-
18
- SA_TYPEMAP = {
19
- int: Integer,
20
- int | None: Integer,
21
- float: Float,
22
- str: String,
23
- bool: Boolean,
24
- datetime: DateTime,
25
- date: Date,
26
- time: Time,
27
- bytes: LargeBinary, # or Binary for smaller data
28
- }
29
-
30
- GLOBAL_ID_SEQ = Sequence("global_id_seq") # define sequence explicitly
31
- SQL_PK = {"metadata": {"SQL": {"primary_key": True}}}
32
-
33
-
34
- def model(table_name: str = None, global_id: bool = False):
35
- """
36
- A decorator that generates a SQLModel from a dataclass.
37
-
38
- Args:
39
- table_name (str): The name of the database table. Defaults to the name of the dataclass.
40
-
41
- Returns:
42
- A decorator that generates a SQLModel from a dataclass.
43
- """
44
-
45
- def sqlmodel(self) -> SQLModel:
46
- return self.__sqlmodel__(**self.__dict__)
47
-
48
- def decorator(cls):
49
- # Check if the class is a dataclass
50
- if not is_dataclass(cls):
51
- raise ValueError("The class must be a dataclass")
52
-
53
- # Generate the SQLModel class
54
- sqlmodel_cls = type(
55
- cls.__name__ + "SQLModel",
56
- (SQLModel,),
57
- {
58
- # Table name is a plural and hence the 's' at the end
59
- "__tablename__": table_name or cls.__name__.lower() + "s",
60
- # Add type annotations to the generated fields
61
- "__annotations__": {
62
- **{field.name: field.type for field in fields(cls)},
63
- **{
64
- "Config": ClassVar,
65
- },
66
- },
67
- # pydantic wants this
68
- "__module__": cls.__module__,
69
- "Config": {"exclude": {"__sqlmodel__", "sqlmodel"}},
70
- **{
71
- field.name: Field(
72
- default_factory=getattr(cls, field.name, None),
73
- # TODO: revisit the idea of using string for unknown types
74
- sa_column=Column(
75
- SA_TYPEMAP.get(field.type, String),
76
- GLOBAL_ID_SEQ if global_id else None,
77
- primary_key=(
78
- field.name == "id"
79
- or field.metadata.get("SQL", {}).get(
80
- "primary_key", False
81
- )
82
- ),
83
- ),
84
- )
85
- for field in fields(cls)
86
- },
87
- },
88
- # For SQLModel's SQLModelMetaClass
89
- table=True,
90
- )
91
-
92
- cls.__sqlmodel__ = sqlmodel_cls
93
- cls.sqlmodel = sqlmodel
94
- return cls
95
-
96
- return decorator
@@ -1,9 +0,0 @@
1
- aioitertools
2
-
3
- [SQL]
4
- duckdb_engine>=0.14.0
5
- pypika>=0.36.5
6
- sqlmodel>=0.0.22
7
-
8
- [graphql]
9
- strawberry>=0.37.1
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes