Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
270 changes: 135 additions & 135 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,31 @@ license = { text = "MIT" }
authors = [{ name = "gazorby", email = "gazorby@pm.me" }]
keywords = ["API", "SQL", "graphql", "sqlalchemy", "strawberry"]
classifiers = [
"Development Status :: 3 - Alpha",
"Environment :: Web Environment",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Natural Language :: English",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Topic :: Database",
"Topic :: Database :: Database Engines/Servers",
"Topic :: Internet :: WWW/HTTP :: HTTP Servers",
"Topic :: Software Development",
"Topic :: Software Development :: Libraries",
"Topic :: Software Development :: Libraries :: Python Modules",
"Typing :: Typed",
"Development Status :: 3 - Alpha",
"Environment :: Web Environment",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Natural Language :: English",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Topic :: Database",
"Topic :: Database :: Database Engines/Servers",
"Topic :: Internet :: WWW/HTTP :: HTTP Servers",
"Topic :: Software Development",
"Topic :: Software Development :: Libraries",
"Topic :: Software Development :: Libraries :: Python Modules",
"Typing :: Typed",
]
dependencies = [
"msgspec>=0.4.2",
"sqlalchemy>=2.0.10",
"strawberry-graphql",
"typing-extensions"
"msgspec>=0.4.2",
"sqlalchemy>=2.0.10",
"strawberry-graphql",
"typing-extensions"
]

[project.urls]
Expand All @@ -49,44 +49,44 @@ pydantic = ["pydantic"]

[dependency-groups]
dev = [
"debugpy",
"testapp",
{ include-group = "aiosqlite" },
{ include-group = "build" },
{ include-group = "doc" },
{ include-group = "lint" },
{ include-group = "mysql" },
{ include-group = "nox" },
{ include-group = "postgres" },
{ include-group = "test" },
"debugpy",
"testapp",
{ include-group = "aiosqlite" },
{ include-group = "build" },
{ include-group = "doc" },
{ include-group = "lint" },
{ include-group = "mysql" },
{ include-group = "nox" },
{ include-group = "postgres" },
{ include-group = "test" },
]
aiosqlite = ["aiosqlite"]
build = ["bump-my-version", "git-cliff", "unasyncd"]
codeflash = ["codeflash"]
doc = ["git-cliff>=2.6.1"]
lint = [
"ruff",
"slotscheck>=0.16.5",
"tombi>=1.1.0",
"ty>=0.0.40",
"vulture",
"ruff",
"slotscheck>=0.16.5",
"tombi>=1.1.0",
"ty>=0.0.40",
"vulture",
]
mysql = ["asyncmy", "cryptography"]
nox = ["nox-uv", "nox[uv]"]
postgres = ["asyncpg>=0.29.0", "psycopg[binary,pool]>=3.2.3"]
test = [
"covdefaults",
"diff-cover>=10.3.0",
"pytest",
"pytest-asyncio>=0.24",
"pytest-cov",
"pytest-databases[postgres,mysql]",
"pytest-lazy-fixtures",
"pytest-pretty",
"pytest-xdist[psutil]",
"sqlparse",
"syrupy",
"testapp",
"covdefaults",
"diff-cover>=10.3.0",
"pytest",
"pytest-asyncio>=0.24",
"pytest-cov",
"pytest-databases[postgres,mysql]",
"pytest-lazy-fixtures",
"pytest-pretty",
"pytest-xdist[psutil]",
"sqlparse",
"syrupy",
"testapp",
]

[build-system]
Expand Down Expand Up @@ -215,7 +215,7 @@ footer = """
trim = true
# postprocessors
postprocessors = [
# { pattern = '<REPO>', replace = "https://github.com/orhun/git-cliff" }, # replace strawberry URL
# { pattern = '<REPO>', replace = "https://github.com/orhun/git-cliff" }, # replace strawberry URL
]

# render body even when there are no releases to process
Expand All @@ -232,30 +232,30 @@ filter_unconventional = true
split_commits = false
# regex for preprocessing the commit messages
commit_preprocessors = [
# Replace issue numbers
# { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](<REPO>/issues/${2}))"},
# Check spelling of the commit with https://github.com/crate-ci/typos
# If the spelling is incorrect, it will be automatically fixed.
# { pattern = '.*', replace_command = 'typos --write-changes -' },
# Replace issue numbers
# { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](<REPO>/issues/${2}))"},
# Check spelling of the commit with https://github.com/crate-ci/typos
# If the spelling is incorrect, it will be automatically fixed.
# { pattern = '.*', replace_command = 'typos --write-changes -' },
]
# regex for parsing and grouping commits
commit_parsers = [
{ message = "^feat", group = "<!-- 0 -->🚀 Features" },
{ message = "^fix", group = "<!-- 1 -->🐛 Bug Fixes" },
{ message = "^doc", group = "<!-- 3 -->📚 Documentation" },
{ message = "^perf", group = "<!-- 4 -->⚡ Performance" },
{ message = "^refactor", group = "<!-- 2 -->🚜 Refactor" },
{ message = "^style", group = "<!-- 5 -->🎨 Styling" },
{ message = "^test", group = "<!-- 6 -->🧪 Testing" },
{ message = "^build\\(release\\): prepare for", skip = true },
{ message = "^build\\(deps.*\\)", skip = true },
{ message = "^build\\(pr\\)", skip = true },
{ message = "^build\\(pull\\)", skip = true },
{ message = "^build|^ci", group = "<!-- 7 -->⚙️ Miscellaneous Tasks" },
{ body = ".*security", group = "<!-- 8 -->🛡️ Security" },
{ message = "^revert", group = "<!-- 9 -->◀️ Revert" },
{ message = ".*", group = "<!-- 10 -->💼 Other" },
{ body = "$^", skip = true },
{ message = "^feat", group = "<!-- 0 -->🚀 Features" },
{ message = "^fix", group = "<!-- 1 -->🐛 Bug Fixes" },
{ message = "^doc", group = "<!-- 3 -->📚 Documentation" },
{ message = "^perf", group = "<!-- 4 -->⚡ Performance" },
{ message = "^refactor", group = "<!-- 2 -->🚜 Refactor" },
{ message = "^style", group = "<!-- 5 -->🎨 Styling" },
{ message = "^test", group = "<!-- 6 -->🧪 Testing" },
{ message = "^build\\(release\\): prepare for", skip = true },
{ message = "^build\\(deps.*\\)", skip = true },
{ message = "^build\\(pr\\)", skip = true },
{ message = "^build\\(pull\\)", skip = true },
{ message = "^build|^ci", group = "<!-- 7 -->⚙️ Miscellaneous Tasks" },
{ body = ".*security", group = "<!-- 8 -->🛡️ Security" },
{ message = "^revert", group = "<!-- 9 -->◀️ Revert" },
{ message = ".*", group = "<!-- 10 -->💼 Other" },
{ body = "$^", skip = true },
]
# filter out the commits that are not matched by commit parsers
filter_commits = false
Expand All @@ -274,15 +274,15 @@ packages = ["src/strawchemy"]

[tool.pytest.ini_options]
markers = [
"snapshot: snapshot-based tests using syrupy",
"integration: SQLAlchemy integration tests",
"geo: Geoalchemy2 integration tests",
"aiosqlite: SQLite (aiosqlite) Tests",
"sqlite: SQLite Tests",
"asyncmy: MySQL (asyncmy) Tests",
"psycopg_async: SQLAlchemy Postgres (psycopg async) Tests",
"psycopg_sync: SQLAlchemy Postgres (psycopg sync) Tests",
"asyncpg: SQLAlchemy Postgres (asyncpg) Tests",
"snapshot: snapshot-based tests using syrupy",
"integration: SQLAlchemy integration tests",
"geo: Geoalchemy2 integration tests",
"aiosqlite: SQLite (aiosqlite) Tests",
"sqlite: SQLite Tests",
"asyncmy: MySQL (asyncmy) Tests",
"psycopg_async: SQLAlchemy Postgres (psycopg async) Tests",
"psycopg_sync: SQLAlchemy Postgres (psycopg sync) Tests",
"asyncpg: SQLAlchemy Postgres (asyncpg) Tests",
]
testpaths = ["tests"]
xfail_strict = true
Expand All @@ -293,52 +293,52 @@ asyncio_default_fixture_loop_scope = "function"
line-length = 120
target-version = "py310"
exclude = [
".bzr",
".direnv",
".eggs",
".git",
".hg",
".mypy_cache",
".nox",
".tox",
".pants.d",
".ruff_cache",
".svn",
".venv",
"__pypackages__",
"_build",
"buck-out",
"build",
"dist",
"node_modules",
"venv",
"tests/codegen/snapshots",
"alembic/* ",
".bzr",
".direnv",
".eggs",
".git",
".hg",
".mypy_cache",
".nox",
".tox",
".pants.d",
".ruff_cache",
".svn",
".venv",
"__pypackages__",
"_build",
"buck-out",
"build",
"dist",
"node_modules",
"venv",
"tests/codegen/snapshots",
"alembic/* ",
]
src = ["src/strawchemy", "tests"]

[tool.ruff.lint]
select = ["ALL"]
ignore = [
"A005", # flake8-builtins - module {name} shadows a Python standard-library module
"D100", # pydocstyle - missing docstring in public module
"D101", # pydocstyle - missing docstring in public class
"D102", # pydocstyle - missing docstring in public method
"D103", # pydocstyle - missing docstring in public function
"D104", # pydocstyle - missing docstring in public package
"D105", # pydocstyle - missing docstring in magic method
"D106", # pydocstyle - missing docstring in public nested class
"D107", # pydocstyle - missing docstring in __init__
"E501", # pycodestyle - line too long, handled by ruff format
"PLR0913", # pylint - too many arguments
"ARG002", # flake8-unused-argument - unused arguments
"COM812", # flake8-commas - Checks for the absence of trailing commas
"S101", # flake8-bandit - Assert statements, investigate
"ANN401", # flake8-annotations - typing Any disallowed in function arguments
"FBT", # flake8-annotations - boolean trap. Try to remove this in the future
"PT013", # flake8-pytest-style - Incorrect import of pytest
"UP007", # flake8-builtins - Unnecessary `Union` annotation
"UP045", # flake8-builtins - Unnecessary `Optional` annotation
"A005", # flake8-builtins - module {name} shadows a Python standard-library module
"D100", # pydocstyle - missing docstring in public module
"D101", # pydocstyle - missing docstring in public class
"D102", # pydocstyle - missing docstring in public method
"D103", # pydocstyle - missing docstring in public function
"D104", # pydocstyle - missing docstring in public package
"D105", # pydocstyle - missing docstring in magic method
"D106", # pydocstyle - missing docstring in public nested class
"D107", # pydocstyle - missing docstring in __init__
"E501", # pycodestyle - line too long, handled by ruff format
"PLR0913", # pylint - too many arguments
"ARG002", # flake8-unused-argument - unused arguments
"COM812", # flake8-commas - Checks for the absence of trailing commas
"S101", # flake8-bandit - Assert statements, investigate
"ANN401", # flake8-annotations - typing Any disallowed in function arguments
"FBT", # flake8-annotations - boolean trap. Try to remove this in the future
"PT013", # flake8-pytest-style - Incorrect import of pytest
"UP007", # flake8-builtins - Unnecessary `Union` annotation
"UP045", # flake8-builtins - Unnecessary `Optional` annotation
]
# Allow unused variables when underscore-prefixed.
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
Expand Down Expand Up @@ -379,20 +379,20 @@ strict-imports = true

[tool.ty.src]
exclude = [
"**/__pycache__",
"**/.hypothesis",
"**/.mypy_cache",
"**/.pytest_cache",
"**/.ruff_cache",
"**/.venv",
"**/.tox",
"**/.nox",
"**/.uv-python",
"**/build",
"**/dist",
"**/node_modules",
"**/cdk.out",
"**/alembic/versions",
"**/__pycache__",
"**/.hypothesis",
"**/.mypy_cache",
"**/.pytest_cache",
"**/.ruff_cache",
"**/.venv",
"**/.tox",
"**/.nox",
"**/.uv-python",
"**/build",
"**/dist",
"**/node_modules",
"**/cdk.out",
"**/alembic/versions",
]

[tool.ty.environment]
Expand Down
24 changes: 21 additions & 3 deletions src/strawchemy/dto/strawberry.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
from strawchemy.utils.text import camel_to_snake

if TYPE_CHECKING:
from collections.abc import Callable, Hashable, Iterable, Sequence
from collections.abc import Callable, Hashable, Iterable, Iterator, Sequence

from sqlalchemy import ColumnElement

Expand Down Expand Up @@ -466,6 +466,24 @@ class Filter:
def __bool__(self) -> bool:
return bool(self.and_ or self.or_ or self.not_)

def iter_aggregation_filters(self) -> Iterator[AggregationFilter]:
"""Yields every ``AggregationFilter`` in this filter tree in traversal order.

Walks the ``and_``, ``or_`` and ``not_`` branches depth-first.

Yields:
Each aggregation filter found under the AND, OR and NOT branches.
"""
for value in self.and_:
if isinstance(value, AggregationFilter):
yield value
elif isinstance(value, Filter):
yield from value.iter_aggregation_filters()
for value in self.or_:
yield from value.iter_aggregation_filters()
if self.not_ is not None:
yield from self.not_.iter_aggregation_filters()


class OrderByEnum(Enum):
ASC = "ASC"
Expand Down Expand Up @@ -568,8 +586,8 @@ class UnmappedStrawberryGraphQLDTO(StrawchemyObject, StrawberryDTO[ModelT]): ...

class GraphQLFilterDTO(UnmappedStrawberryGraphQLDTO[DeclarativeBase]):
@property
def dto_set_fields(self) -> set[str]:
return {name for name in self.__dto_field_definitions__ if getattr(self, name) is not strawberry.UNSET}
def dto_set_fields(self) -> list[str]:
return [name for name in self.__dto_field_definitions__ if getattr(self, name) is not strawberry.UNSET]


class AggregateDTO(UnmappedStrawberryGraphQLDTO[DeclarativeBase]): ...
Expand Down
Loading
Loading