fquery 0.3__tar.gz → 0.4__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 (41) hide show
  1. {fquery-0.3 → fquery-0.4}/PKG-INFO +2 -1
  2. {fquery-0.3 → fquery-0.4}/fquery/malloy_builder.py +0 -4
  3. fquery-0.4/fquery/polars_builder.py +53 -0
  4. fquery-0.4/fquery/pydantic.py +46 -0
  5. {fquery-0.3 → fquery-0.4}/fquery/query.py +11 -0
  6. {fquery-0.3 → fquery-0.4}/fquery/sqlmodel.py +3 -0
  7. {fquery-0.3 → fquery-0.4}/fquery.egg-info/PKG-INFO +2 -1
  8. {fquery-0.3 → fquery-0.4}/fquery.egg-info/SOURCES.txt +4 -0
  9. {fquery-0.3 → fquery-0.4}/fquery.egg-info/requires.txt +3 -0
  10. {fquery-0.3 → fquery-0.4}/setup.py +2 -1
  11. fquery-0.4/tests/test_polars.py +36 -0
  12. fquery-0.4/tests/test_pydantic.py +34 -0
  13. {fquery-0.3 → fquery-0.4}/LICENSE +0 -0
  14. {fquery-0.3 → fquery-0.4}/README.md +0 -0
  15. {fquery-0.3 → fquery-0.4}/fquery/__init__.py +0 -0
  16. {fquery-0.3 → fquery-0.4}/fquery/aitertools.py +0 -0
  17. {fquery-0.3 → fquery-0.4}/fquery/async_utils.py +0 -0
  18. {fquery-0.3 → fquery-0.4}/fquery/django.py +0 -0
  19. {fquery-0.3 → fquery-0.4}/fquery/execute.py +0 -0
  20. {fquery-0.3 → fquery-0.4}/fquery/fgraphql.py +0 -0
  21. {fquery-0.3 → fquery-0.4}/fquery/resolve.py +0 -0
  22. {fquery-0.3 → fquery-0.4}/fquery/sql_builder.py +0 -0
  23. {fquery-0.3 → fquery-0.4}/fquery/view_model.py +0 -0
  24. {fquery-0.3 → fquery-0.4}/fquery/visitor.py +0 -0
  25. {fquery-0.3 → fquery-0.4}/fquery/walk.py +0 -0
  26. {fquery-0.3 → fquery-0.4}/fquery.egg-info/dependency_links.txt +0 -0
  27. {fquery-0.3 → fquery-0.4}/fquery.egg-info/top_level.txt +0 -0
  28. {fquery-0.3 → fquery-0.4}/pyproject.toml +0 -0
  29. {fquery-0.3 → fquery-0.4}/setup.cfg +0 -0
  30. {fquery-0.3 → fquery-0.4}/tests/__init__.py +0 -0
  31. {fquery-0.3 → fquery-0.4}/tests/async_test.py +0 -0
  32. {fquery-0.3 → fquery-0.4}/tests/benchmark.py +0 -0
  33. {fquery-0.3 → fquery-0.4}/tests/django_example_model.py +0 -0
  34. {fquery-0.3 → fquery-0.4}/tests/graphql_mock_user.py +0 -0
  35. {fquery-0.3 → fquery-0.4}/tests/mock_user.py +0 -0
  36. {fquery-0.3 → fquery-0.4}/tests/test_malloy.py +0 -0
  37. {fquery-0.3 → fquery-0.4}/tests/test_operators.py +0 -0
  38. {fquery-0.3 → fquery-0.4}/tests/test_sql.py +0 -0
  39. {fquery-0.3 → fquery-0.4}/tests/test_sqlmodel.py +0 -0
  40. {fquery-0.3 → fquery-0.4}/tests/test_walk.py +0 -0
  41. {fquery-0.3 → fquery-0.4}/tests/test_walk_obj.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fquery
3
- Version: 0.3
3
+ Version: 0.4
4
4
  Summary: A graph query engine
5
5
  Home-page: https://github.com/adsharma/fquery
6
6
  Classifier: Programming Language :: Python :: 3
@@ -9,6 +9,7 @@ Classifier: Operating System :: OS Independent
9
9
  Requires-Python: >=3.6
10
10
  Description-Content-Type: text/markdown
11
11
  Provides-Extra: SQL
12
+ Provides-Extra: df
12
13
  Provides-Extra: graphql
13
14
  License-File: LICENSE
14
15
 
@@ -1,7 +1,3 @@
1
- # Copyright (c) Facebook, Inc. and its affiliates.
2
- #
3
- # This source code is licensed under the MIT license found in the
4
- # LICENSE file in the root directory of this source tree.
5
1
  import ast
6
2
  import operator
7
3
 
@@ -0,0 +1,53 @@
1
+ import ast
2
+ import operator
3
+
4
+ import polars as pl
5
+
6
+ from .visitor import Visitor
7
+
8
+ # inspired from pandas.core.computation.ops
9
+ _cmp_ops_syms = (">", "<", ">=", "<=", "==", "!=")
10
+ _cmp_ops_funcs = (
11
+ operator.gt,
12
+ operator.lt,
13
+ operator.ge,
14
+ operator.le,
15
+ operator.eq,
16
+ operator.ne,
17
+ )
18
+ _cmp_ops_dict = dict(zip(_cmp_ops_syms, _cmp_ops_funcs))
19
+
20
+
21
+ class PolarsBuilderVisitor(Visitor):
22
+
23
+ def __init__(self, id1s):
24
+ self.polars = None
25
+ self.polars_stack = []
26
+ self.visited = set()
27
+
28
+ async def visit_leaf(self, query):
29
+ # TODO: make this columnar and real lazy instead of faking laziness
30
+ self.polars = pl.DataFrame(await query.as_list()).lazy()
31
+ while self.polars_stack:
32
+ func, params = self.polars_stack.pop()
33
+ self.polars = getattr(self.polars, func)(params)
34
+
35
+ async def visit_project(self, query):
36
+ self.polars_stack.append(("select", query.projector))
37
+ await self.visit(query.child)
38
+
39
+ async def visit_take(self, query):
40
+ self.polars_stack.append(("limit", query._count))
41
+ await self.visit(query.child)
42
+
43
+ async def visit_where(self, query):
44
+ left, op, right = query._expr.value.split()
45
+ right = ast.literal_eval(right)
46
+ table, field = left.split(".") if "." in left else (self.malloy, left)
47
+ self.polars_stack.append(("filter", (_cmp_ops_dict[op](pl.col(field), right))))
48
+ await self.visit(query.child)
49
+
50
+ async def visit_order_by(self, query):
51
+ table, field = query._expr.value.split(".")
52
+ self.polars_stack.append(("sort", field))
53
+ await self.visit(query.child)
@@ -0,0 +1,46 @@
1
+ import dataclasses
2
+ from dataclasses import dataclass, fields
3
+ from typing import Type
4
+
5
+ from pydantic import BaseModel, ConfigDict, Field
6
+
7
+
8
+ def pydantic(cls):
9
+ return model(dataclass(kw_only=True)(cls))
10
+
11
+
12
+ def validator(self) -> BaseModel:
13
+ attrs = {name: getattr(self, name) for name in self.__pydantic__.model_fields}
14
+ return self.__pydantic__(**attrs)
15
+
16
+
17
+ def get_field_def(cls, field):
18
+ # if the dataclass has a default_factory, or a default value, use it in pydantic Field
19
+ kwargs = {}
20
+ if not isinstance(field.default, dataclasses._MISSING_TYPE):
21
+ kwargs["default"] = field.default
22
+ if not isinstance(field.default_factory, dataclasses._MISSING_TYPE):
23
+ kwargs["default_factory"] = field.default_factory
24
+ return Field(**kwargs)
25
+
26
+
27
+ def model(cls: Type) -> Type:
28
+ """
29
+ Decorator to convert a dataclass to a Pydantic model.
30
+ """
31
+ # Generate the SQLModel class
32
+ pydantic_cls = type(
33
+ cls.__name__ + "Model",
34
+ (BaseModel,),
35
+ {
36
+ # Add type annotations to the generated fields
37
+ "__annotations__": {**{field.name: field.type for field in fields(cls)}},
38
+ # Actual field defs
39
+ **{field.name: get_field_def(cls, field) for field in fields(cls)},
40
+ },
41
+ )
42
+ cls.__pydantic__ = pydantic_cls
43
+ cls.model_config = ConfigDict(extra="ignore")
44
+ cls.validator = validator
45
+
46
+ return cls
@@ -12,6 +12,7 @@ from typing import Dict, List, Optional, Tuple, Type, Union
12
12
  from .async_utils import wait_for
13
13
  from .execute import AbstractSyntaxTreeVisitor
14
14
  from .malloy_builder import MalloyBuilderVisitor
15
+ from .polars_builder import PolarsBuilderVisitor
15
16
  from .sql_builder import SQLBuilderVisitor
16
17
  from .view_model import ViewModel, get_edges, get_return_type
17
18
  from .walk import (
@@ -256,6 +257,16 @@ class Query:
256
257
  wait_for(visitor.visit(self))
257
258
  return visitor.malloy
258
259
 
260
+ def to_polars(self) -> Tree:
261
+ visitor = PolarsBuilderVisitor([])
262
+ wait_for(visitor.visit(self))
263
+ return visitor.polars.collect()
264
+
265
+ async def to_async_polars(self) -> Tree:
266
+ visitor = PolarsBuilderVisitor([])
267
+ await visitor.visit(self)
268
+ return await visitor.polars.collect_async()
269
+
259
270
  def batch_resolve_objs(self) -> List[Dict[str, List[ViewModel]]]:
260
271
  return [{str(None): [o for o in (self.resolve_obj(i) for i in self.ids) if o]}]
261
272
 
@@ -3,6 +3,7 @@ from dataclasses import _FIELD, dataclass, field, fields, is_dataclass
3
3
  from datetime import date, datetime, time
4
4
  from typing import (
5
5
  ClassVar,
6
+ Dict,
6
7
  ForwardRef,
7
8
  List,
8
9
  Optional,
@@ -14,6 +15,7 @@ from typing import (
14
15
 
15
16
  import inflection
16
17
  from sqlalchemy import (
18
+ JSON,
17
19
  Boolean,
18
20
  Date,
19
21
  DateTime,
@@ -37,6 +39,7 @@ SA_TYPEMAP = {
37
39
  date: Date,
38
40
  time: Time,
39
41
  bytes: LargeBinary, # or Binary for smaller data
42
+ Dict: JSON,
40
43
  }
41
44
 
42
45
  GLOBAL_ID_SEQ = Sequence("global_id_seq") # define sequence explicitly
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fquery
3
- Version: 0.3
3
+ Version: 0.4
4
4
  Summary: A graph query engine
5
5
  Home-page: https://github.com/adsharma/fquery
6
6
  Classifier: Programming Language :: Python :: 3
@@ -9,6 +9,7 @@ Classifier: Operating System :: OS Independent
9
9
  Requires-Python: >=3.6
10
10
  Description-Content-Type: text/markdown
11
11
  Provides-Extra: SQL
12
+ Provides-Extra: df
12
13
  Provides-Extra: graphql
13
14
  License-File: LICENSE
14
15
 
@@ -10,6 +10,8 @@ fquery/django.py
10
10
  fquery/execute.py
11
11
  fquery/fgraphql.py
12
12
  fquery/malloy_builder.py
13
+ fquery/polars_builder.py
14
+ fquery/pydantic.py
13
15
  fquery/query.py
14
16
  fquery/resolve.py
15
17
  fquery/sql_builder.py
@@ -30,6 +32,8 @@ tests/graphql_mock_user.py
30
32
  tests/mock_user.py
31
33
  tests/test_malloy.py
32
34
  tests/test_operators.py
35
+ tests/test_polars.py
36
+ tests/test_pydantic.py
33
37
  tests/test_sql.py
34
38
  tests/test_sqlmodel.py
35
39
  tests/test_walk.py
@@ -6,5 +6,8 @@ inflection>=0.5.1
6
6
  pypika>=0.36.5
7
7
  sqlmodel@ git+https://github.com/adsharma/sqlmodel.git@sqlmodel_rebuild
8
8
 
9
+ [df]
10
+ polars>=0.12.0
11
+
9
12
  [graphql]
10
13
  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.3",
14
+ version="0.4",
15
15
  description="A graph query engine",
16
16
  url="https://github.com/adsharma/fquery",
17
17
  long_description=long_description,
@@ -34,5 +34,6 @@ setuptools.setup(
34
34
  "inflection >= 0.5.1",
35
35
  ],
36
36
  "graphql": ["strawberry >= 0.37.1"],
37
+ "df": ["polars >= 0.12.0"],
37
38
  },
38
39
  )
@@ -0,0 +1,36 @@
1
+ import ast
2
+ import random
3
+ import unittest
4
+
5
+ import polars as pl
6
+ from polars.testing import assert_frame_equal
7
+
8
+ from .mock_user import UserQuery
9
+
10
+
11
+ class PolarsTests(unittest.TestCase):
12
+ def setUp(self):
13
+ random.seed(100)
14
+ self.maxDiff = None
15
+
16
+ def test_project(self):
17
+ df = (
18
+ UserQuery(range(1, 10))
19
+ .project([":id", "name", "age"])
20
+ .where(ast.Expr("user.age >= 16"))
21
+ .order_by(ast.Expr("user.age"))
22
+ .take(3)
23
+ .to_polars()
24
+ )
25
+ expected = pl.DataFrame(
26
+ {
27
+ ":id": [1, 4, 2],
28
+ "name": ["id1", "id4", "id2"],
29
+ "age": [16, 16, 17],
30
+ }
31
+ )
32
+ assert_frame_equal(expected, df)
33
+
34
+
35
+ if __name__ == "__main__":
36
+ unittest.main()
@@ -0,0 +1,34 @@
1
+ from dataclasses import is_dataclass
2
+
3
+ import pytest
4
+ from pydantic import BaseModel, ValidationError
5
+
6
+ from fquery.pydantic import pydantic
7
+
8
+
9
+ @pydantic
10
+ class User:
11
+ name: str
12
+ age: int
13
+ is_active: bool = True
14
+
15
+
16
+ def test_pydantic():
17
+ u1 = User(name="John Doe", age=42)
18
+ u2 = User(name="John Doe", age=42, is_active=False)
19
+ assert is_dataclass(u1)
20
+ assert is_dataclass(u2)
21
+
22
+ v1 = u1.validator()
23
+ v2 = u2.validator()
24
+ assert isinstance(v1, BaseModel)
25
+ assert isinstance(v2, BaseModel)
26
+
27
+ assert v1.model_dump() == u1.__dict__
28
+ assert v2.model_dump() == u2.__dict__
29
+
30
+
31
+ def test_pydantic_fail():
32
+ u1 = User(name="John Doe", age=42.3)
33
+ with pytest.raises(ValidationError):
34
+ _ = u1.validator()
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