Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -192,3 +192,5 @@ mise.local.toml
.windsurf

CLAUDE.md

.serena
28 changes: 13 additions & 15 deletions mise.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,6 @@ description = "Install dependencies"
hide = true
run = "uv sync --all-extras --dev"

[tasks."_install:geo"]
description = "Install dependencies with geo extras"
hide = true
run = "uv sync --extra=geo --dev"

[tasks.install]
description = "Install dependencies and pre-commit hooks"
Expand Down Expand Up @@ -194,7 +190,6 @@ run = "uv run nox --json -t tests -l | jq 'map(.name) | unique'"

[tasks."ruff:check"]
description = "Check ruff formatting"
depends = "_install"
run = "uv run ruff check"

[tasks."ruff:fix"]
Expand All @@ -211,7 +206,6 @@ run = "uv run ruff format --check"

[tasks.pyright]
description = "Run basedpyright"
depends = "_install"
run = "uv run basedpyright"

[tasks.vulture]
Expand All @@ -221,7 +215,7 @@ run = "uv run --only-group lint vulture"
[tasks.lint]
description = "Lint the code"
alias = "l"
depends = ["vulture", "pyright", "ruff:check", "ruff:format:check"]
depends = ["vulture", "pyright", "ruff:check", "ruff:format:check", "slotscheck"]

[tasks."lint:pre-commit"]
description = "Lint the code in pre-commit hook"
Expand All @@ -232,6 +226,10 @@ description = "Run pre-commit checks"
depends = "install:pre-commit"
run = "pre-commit run --color=always --all-files"

[tasks.slotscheck]
description = "Run slotscheck"
run = "uv run slotscheck src/strawchemy"

# ###############
# Tools
# ###############
Expand All @@ -247,14 +245,14 @@ description = "Clean working directory"
alias = "c"
confirm = "Are you sure you want to clean the working directory? This will remove test caches, build artifacts, and other temporary files."
run = [
"rm -rf {{vars.cleanable_paths}} >/dev/null 2>&1",
"find . -name '*.egg-info' -exec rm -rf {} + >/dev/null 2>&1",
"find . -type f -name '*.egg' -exec rm -f {} + >/dev/null 2>&1",
"find . -name '*.pyc' -exec rm -f {} + >/dev/null 2>&1",
"find . -name '*.pyo' -exec rm -f {} + >/dev/null 2>&1",
"find . -name '*~' -exec rm -f {} + >/dev/null 2>&1",
"find . -name '__pycache__' -exec rm -rf {} + >/dev/null 2>&1",
"find . -name '.ipynb_checkpoints' -exec rm -rf {} + >/dev/null 2>&1",
"rm -rf {{vars.cleanable_paths}} >/dev/null 2>&1",
"find . -name '*.egg-info' -exec rm -rf {} + >/dev/null 2>&1",
"find . -type f -name '*.egg' -exec rm -f {} + >/dev/null 2>&1",
"find . -name '*.pyc' -exec rm -f {} + >/dev/null 2>&1",
"find . -name '*.pyo' -exec rm -f {} + >/dev/null 2>&1",
"find . -name '*~' -exec rm -f {} + >/dev/null 2>&1",
"find . -name '__pycache__' -exec rm -rf {} + >/dev/null 2>&1",
"find . -name '.ipynb_checkpoints' -exec rm -rf {} + >/dev/null 2>&1",
]

[tasks."render:usage"]
Expand Down
21 changes: 19 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ dev = [
]
codeflash = ["codeflash"]
doc = ["git-cliff>=2.6.1"]
lint = ["basedpyright", "ruff", "vulture"]
lint = ["basedpyright", "ruff", "vulture", "slotscheck>=0.16.5"]
mysql = ["asyncmy", "cryptography"]
postgres = ["asyncpg>=0.29.0", "psycopg[binary,pool]>=3.2.3"]
test = [
Expand Down Expand Up @@ -309,7 +309,6 @@ asyncio_default_fixture_loop_scope = "function"

[tool.ruff]
line-length = 120
fix = true
target-version = "py310"
exclude = [
".bzr",
Expand Down Expand Up @@ -378,6 +377,24 @@ known-first-party = ["strawchemy", "tests"]
[tool.ruff.lint.flake8-tidy-imports]
ban-relative-imports = "all"

[tool.slotscheck]
exclude-modules = '''
(
(^|\.)test_
|^tests\.*
|^tools\.*
|^docs\.*
|^examples\.*
|^sqlalchemy\.(
testing
|ext\.mypy # see slotscheck/issues/178
)
)
'''
include-modules = "strawchemy.*"
require-superclass = false
strict-imports = true

[tool.unasyncd]
add_editors_note = true
ruff_fix = true
Expand Down
135 changes: 135 additions & 0 deletions src/strawchemy/factories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
"""Factory container for organizing Strawchemy DTO factories."""

from __future__ import annotations

from dataclasses import dataclass
from functools import partial
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
from strawchemy.mapper import Strawchemy
from strawchemy.strawberry.factories.aggregations import EnumDTOFactory
from strawchemy.strawberry.factories.inputs import AggregateFilterDTOFactory, BooleanFilterDTOFactory
from strawchemy.strawberry.factories.types import (
DistinctOnFieldsDTOFactory,
InputFactory,
OrderByDTOFactory,
RootAggregateTypeDTOFactory,
TypeDTOFactory,
UpsertConflictFieldsDTOFactory,
)


@dataclass
class StrawchemyFactories:
"""Container for all Strawchemy DTO factories.

This class encapsulates the initialization and management of all factory
instances used by Strawchemy, providing a cleaner separation of concerns
and easier testing.

Attributes:
aggregate_filter: Factory for aggregate filter DTOs.
order_by: Factory for order by DTOs.
distinct_on_enum: Factory for distinct on enum DTOs.
type_factory: Factory for output type DTOs.
input_factory: Factory for input type DTOs.
aggregation: Factory for root aggregate type DTOs.
enum_factory: Factory for enum DTOs.
filter_factory: Factory for boolean filter DTOs.
upsert_conflict: Factory for upsert conflict fields DTOs.
"""

aggregate_filter: AggregateFilterDTOFactory
order_by: OrderByDTOFactory
distinct_on_enum: DistinctOnFieldsDTOFactory
type_factory: TypeDTOFactory # type: ignore[type-arg]
input_factory: InputFactory # type: ignore[type-arg]
aggregation: RootAggregateTypeDTOFactory # type: ignore[type-arg]
enum_factory: EnumDTOFactory
filter_factory: BooleanFilterDTOFactory
upsert_conflict: UpsertConflictFieldsDTOFactory

@classmethod
def create(cls, mapper: Strawchemy) -> StrawchemyFactories:
"""Create all factories with proper dependencies.

Args:
mapper: The Strawchemy instance that will own these factories.

Returns:
A StrawchemyFactories instance with all factories initialized.
"""
# Imports inside method to avoid circular dependencies at module load time
from strawchemy.dto.backend.strawberry import StrawberrryDTOBackend # noqa: PLC0415
from strawchemy.strawberry.dto import MappedStrawberryGraphQLDTO # noqa: PLC0415
from strawchemy.strawberry.factories.aggregations import EnumDTOFactory # noqa: PLC0415
from strawchemy.strawberry.factories.enum import ( # noqa: PLC0415
EnumDTOBackend,
UpsertConflictFieldsEnumDTOBackend,
)
from strawchemy.strawberry.factories.inputs import ( # noqa: PLC0415
AggregateFilterDTOFactory,
BooleanFilterDTOFactory,
)
from strawchemy.strawberry.factories.types import ( # noqa: PLC0415
DistinctOnFieldsDTOFactory,
InputFactory,
OrderByDTOFactory,
RootAggregateTypeDTOFactory,
TypeDTOFactory,
UpsertConflictFieldsDTOFactory,
)
Comment on lines +64 to +82
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Remove unused noqa directives.

Static analysis indicates that PLC0415 is not enabled in your ruff configuration, making these noqa comments unnecessary. The internal imports are still valid and intentional for avoiding circular dependencies.

🔎 Proposed fix
-        from strawchemy.dto.backend.strawberry import StrawberrryDTOBackend  # noqa: PLC0415
-        from strawchemy.strawberry.dto import MappedStrawberryGraphQLDTO  # noqa: PLC0415
-        from strawchemy.strawberry.factories.aggregations import EnumDTOFactory  # noqa: PLC0415
-        from strawchemy.strawberry.factories.enum import (  # noqa: PLC0415
+        from strawchemy.dto.backend.strawberry import StrawberrryDTOBackend
+        from strawchemy.strawberry.dto import MappedStrawberryGraphQLDTO
+        from strawchemy.strawberry.factories.aggregations import EnumDTOFactory
+        from strawchemy.strawberry.factories.enum import (
             EnumDTOBackend,
             UpsertConflictFieldsEnumDTOBackend,
         )
-        from strawchemy.strawberry.factories.inputs import (  # noqa: PLC0415
+        from strawchemy.strawberry.factories.inputs import (
             AggregateFilterDTOFactory,
             BooleanFilterDTOFactory,
         )
-        from strawchemy.strawberry.factories.types import (  # noqa: PLC0415
+        from strawchemy.strawberry.factories.types import (
             DistinctOnFieldsDTOFactory,
             InputFactory,
             OrderByDTOFactory,
             RootAggregateTypeDTOFactory,
             TypeDTOFactory,
             UpsertConflictFieldsDTOFactory,
         )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
from strawchemy.dto.backend.strawberry import StrawberrryDTOBackend # noqa: PLC0415
from strawchemy.strawberry.dto import MappedStrawberryGraphQLDTO # noqa: PLC0415
from strawchemy.strawberry.factories.aggregations import EnumDTOFactory # noqa: PLC0415
from strawchemy.strawberry.factories.enum import ( # noqa: PLC0415
EnumDTOBackend,
UpsertConflictFieldsEnumDTOBackend,
)
from strawchemy.strawberry.factories.inputs import ( # noqa: PLC0415
AggregateFilterDTOFactory,
BooleanFilterDTOFactory,
)
from strawchemy.strawberry.factories.types import ( # noqa: PLC0415
DistinctOnFieldsDTOFactory,
InputFactory,
OrderByDTOFactory,
RootAggregateTypeDTOFactory,
TypeDTOFactory,
UpsertConflictFieldsDTOFactory,
)
from strawchemy.dto.backend.strawberry import StrawberrryDTOBackend
from strawchemy.strawberry.dto import MappedStrawberryGraphQLDTO
from strawchemy.strawberry.factories.aggregations import EnumDTOFactory
from strawchemy.strawberry.factories.enum import (
EnumDTOBackend,
UpsertConflictFieldsEnumDTOBackend,
)
from strawchemy.strawberry.factories.inputs import (
AggregateFilterDTOFactory,
BooleanFilterDTOFactory,
)
from strawchemy.strawberry.factories.types import (
DistinctOnFieldsDTOFactory,
InputFactory,
OrderByDTOFactory,
RootAggregateTypeDTOFactory,
TypeDTOFactory,
UpsertConflictFieldsDTOFactory,
)
🧰 Tools
🪛 Ruff (0.14.8)

64-64: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


65-65: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


66-66: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


67-67: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


71-71: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


75-75: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)

🤖 Prompt for AI Agents
In src/strawchemy/factories.py around lines 64 to 82, multiple import lines
include unnecessary "# noqa: PLC0415" directives; remove these unused noqa
comments from each import (e.g., after StrawberrryDTOBackend,
MappedStrawberryGraphQLDTO, EnumDTOFactory, EnumDTOBackend,
UpsertConflictFieldsEnumDTOBackend, AggregateFilterDTOFactory,
BooleanFilterDTOFactory, DistinctOnFieldsDTOFactory, InputFactory,
OrderByDTOFactory, RootAggregateTypeDTOFactory, TypeDTOFactory,
UpsertConflictFieldsDTOFactory) so the imports remain but the redundant noqa
annotations are deleted.


config = mapper.config

# Create backend instances
strawberry_backend = StrawberrryDTOBackend(MappedStrawberryGraphQLDTO)
enum_backend = EnumDTOBackend(config.auto_snake_case)
upsert_conflict_fields_enum_backend = UpsertConflictFieldsEnumDTOBackend(
config.inspector, config.auto_snake_case
)

# Create factory instances
aggregate_filter = AggregateFilterDTOFactory(mapper)
order_by = OrderByDTOFactory(mapper)
distinct_on_enum = DistinctOnFieldsDTOFactory(config.inspector)
type_factory = TypeDTOFactory(mapper, strawberry_backend, order_by_factory=order_by)
input_factory = InputFactory(mapper, strawberry_backend)
aggregation = RootAggregateTypeDTOFactory(mapper, strawberry_backend, type_factory=type_factory)
enum_factory = EnumDTOFactory(config.inspector, enum_backend)
filter_factory = BooleanFilterDTOFactory(mapper, aggregate_filter_factory=aggregate_filter)
upsert_conflict = UpsertConflictFieldsDTOFactory(config.inspector, upsert_conflict_fields_enum_backend)

return cls(
aggregate_filter=aggregate_filter,
order_by=order_by,
distinct_on_enum=distinct_on_enum,
type_factory=type_factory,
input_factory=input_factory,
aggregation=aggregation,
enum_factory=enum_factory,
filter_factory=filter_factory,
upsert_conflict=upsert_conflict,
)

def create_public_api(self) -> dict[str, Any]:
"""Create the public API mappings for factory methods.

Returns:
A dictionary mapping public API names to factory methods.
"""
return {
"filter": self.filter_factory.input,
"aggregate_filter": partial(self.aggregate_filter.input, mode="aggregate_filter"),
"distinct_on": self.distinct_on_enum.decorator,
"input": self.input_factory.input,
"create_input": partial(self.input_factory.input, mode="create_input"),
"pk_update_input": partial(self.input_factory.input, mode="update_by_pk_input"),
"filter_update_input": partial(self.input_factory.input, mode="update_by_filter_input"),
"order": partial(self.order_by.input, mode="order_by"),
"type": self.type_factory.type,
"aggregate": partial(self.aggregation.type, mode="aggregate_type"),
"upsert_update_fields": self.enum_factory.input,
"upsert_conflict_fields": self.upsert_conflict.input,
}
Loading
Loading