strawberry-orm 0.6.0__tar.gz → 0.7.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.
Files changed (24) hide show
  1. {strawberry_orm-0.6.0 → strawberry_orm-0.7.0}/PKG-INFO +215 -3
  2. {strawberry_orm-0.6.0 → strawberry_orm-0.7.0}/README.md +214 -2
  3. {strawberry_orm-0.6.0 → strawberry_orm-0.7.0}/pyproject.toml +3 -2
  4. {strawberry_orm-0.6.0 → strawberry_orm-0.7.0}/src/strawberry_orm/__init__.py +8 -0
  5. {strawberry_orm-0.6.0 → strawberry_orm-0.7.0}/src/strawberry_orm/_async.py +2 -1
  6. {strawberry_orm-0.6.0 → strawberry_orm-0.7.0}/src/strawberry_orm/backends/_base.py +321 -1
  7. {strawberry_orm-0.6.0 → strawberry_orm-0.7.0}/src/strawberry_orm/backends/django.py +117 -40
  8. {strawberry_orm-0.6.0 → strawberry_orm-0.7.0}/src/strawberry_orm/backends/protocol.py +8 -0
  9. {strawberry_orm-0.6.0 → strawberry_orm-0.7.0}/src/strawberry_orm/backends/sqlalchemy.py +138 -42
  10. {strawberry_orm-0.6.0 → strawberry_orm-0.7.0}/src/strawberry_orm/backends/tortoise.py +126 -48
  11. {strawberry_orm-0.6.0 → strawberry_orm-0.7.0}/src/strawberry_orm/core.py +20 -3
  12. {strawberry_orm-0.6.0 → strawberry_orm-0.7.0}/src/strawberry_orm/fields.py +2 -1
  13. {strawberry_orm-0.6.0 → strawberry_orm-0.7.0}/src/strawberry_orm/filters.py +50 -1
  14. {strawberry_orm-0.6.0 → strawberry_orm-0.7.0}/src/strawberry_orm/mutations.py +185 -42
  15. strawberry_orm-0.7.0/src/strawberry_orm/policy.py +140 -0
  16. {strawberry_orm-0.6.0 → strawberry_orm-0.7.0}/src/strawberry_orm/relay/connection.py +1 -2
  17. strawberry_orm-0.7.0/src/strawberry_orm/repo.py +284 -0
  18. {strawberry_orm-0.6.0 → strawberry_orm-0.7.0}/src/strawberry_orm/types.py +2 -1
  19. {strawberry_orm-0.6.0 → strawberry_orm-0.7.0}/src/strawberry_orm/backends/__init__.py +0 -0
  20. {strawberry_orm-0.6.0 → strawberry_orm-0.7.0}/src/strawberry_orm/optimizer/__init__.py +0 -0
  21. {strawberry_orm-0.6.0 → strawberry_orm-0.7.0}/src/strawberry_orm/optimizer/extension.py +0 -0
  22. {strawberry_orm-0.6.0 → strawberry_orm-0.7.0}/src/strawberry_orm/optimizer/store.py +0 -0
  23. {strawberry_orm-0.6.0 → strawberry_orm-0.7.0}/src/strawberry_orm/py.typed +0 -0
  24. {strawberry_orm-0.6.0 → strawberry_orm-0.7.0}/src/strawberry_orm/relay/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: strawberry-orm
3
- Version: 0.6.0
3
+ Version: 0.7.0
4
4
  Summary: Unified, backend-agnostic ORM abstraction for Strawberry GraphQL
5
5
  Author: James Davidson, Patrick Arminio
6
6
  Author-email: James Davidson <jamie.t.davidson@gmail.com>, Patrick Arminio <patrick.arminio@gmail.com>
@@ -46,6 +46,7 @@ Backend-agnostic schema generation for [Strawberry GraphQL](https://strawberry.r
46
46
  - [Backends](#backends)
47
47
  - [Defining Types](#defining-types)
48
48
  - [Filters and Ordering](#filters-and-ordering)
49
+ - [Custom Filters and Ordering](#custom-filters-and-ordering)
49
50
  - [Mutations](#mutations)
50
51
  - [Relay Integration](#relay-integration)
51
52
  - [Query Optimization](#query-optimization)
@@ -129,7 +130,7 @@ class PostType:
129
130
 
130
131
  CreatePostInput = orm.input(Post, include=["title", "body", "author_id"])
131
132
  CreateTagInput = orm.input(Tag, include=["name"])
132
- TagRef = orm.ref(Tag, create=CreateTagInput, delete=True)
133
+ TagRef = orm.ref(Tag, create=CreateTagInput, unlink=True, delete=True)
133
134
 
134
135
  @strawberry.type
135
136
  class Mutation:
@@ -517,6 +518,217 @@ Registration order matters: define related orders *before* the parent (e.g. `orm
517
518
 
518
519
  ---
519
520
 
521
+ ## Custom Filters and Ordering
522
+
523
+ `orm.filter()` and `orm.order()` auto-generate types from model introspection. When you need filter logic that goes beyond column lookups — full-text search across multiple fields, subquery-based conditions, or ordering by computed values — use `orm.filter_type()` and `orm.order_type()` with the `@filter_field` and `@order_field` decorators.
524
+
525
+ ### Custom Filter Types
526
+
527
+ `orm.filter_type(Model)` is a class decorator. Annotate fields with `auto` for standard lookups (identical to what `orm.filter()` generates). Add methods decorated with `@filter_field` for custom logic:
528
+
529
+ ```python
530
+ from strawberry_orm import StrawberryORM, filter_field, auto
531
+
532
+ orm = StrawberryORM("sqlalchemy", dialect="postgresql", session_getter=...)
533
+
534
+ @orm.filter_type(User)
535
+ class UserFilter:
536
+ name: auto # standard StringLookup
537
+ email: auto # standard StringLookup
538
+
539
+ @filter_field
540
+ def search(self, value: str, query):
541
+ """Full-text search across name and email."""
542
+ from sqlalchemy import or_
543
+ return query.where(
544
+ or_(User.name.ilike(f"%{value}%"), User.email.ilike(f"%{value}%"))
545
+ )
546
+
547
+ @filter_field
548
+ def has_posts(self, value: bool, query):
549
+ """Filter users who have (or lack) any posts."""
550
+ from sqlalchemy import func, select
551
+ subq = (
552
+ select(func.count(Post.id))
553
+ .where(Post.author_id == User.id)
554
+ .correlate(User)
555
+ .scalar_subquery()
556
+ )
557
+ if value:
558
+ return query.where(subq > 0)
559
+ return query.where(subq == 0)
560
+ ```
561
+
562
+ Each `@filter_field` method must:
563
+
564
+ - Have a `value` parameter with a **type annotation** — this becomes the GraphQL input type for the field.
565
+ - Have a `query` parameter — receives the backend's native query object (Django `QuerySet`, SQLAlchemy `Select`, or Tortoise `QuerySet`).
566
+ - Return the modified query.
567
+ - Optionally accept an `info` parameter to receive the Strawberry `Info` context.
568
+
569
+ The generated GraphQL input places custom fields as top-level keys alongside `field`, `object`, `all`, `any`, `not`, and `oneOf`:
570
+
571
+ ```graphql
572
+ input UserFilter @oneOf {
573
+ field: UserField # auto-generated scalar lookups
574
+ object: UserObject # auto-generated relation lookups (if any)
575
+ search: String # custom
576
+ hasPosts: Boolean # custom
577
+ all: [UserFilter!]
578
+ any: [UserFilter!]
579
+ not: UserFilter
580
+ oneOf: [UserFilter!]
581
+ }
582
+ ```
583
+
584
+ Since filters are `@oneOf`, combine custom filters with standard lookups using `all` or `any`:
585
+
586
+ ```graphql
587
+ {
588
+ users(filter: { all: [
589
+ { search: "john" },
590
+ { field: { email: { contains: "example.com" } } }
591
+ ] }) {
592
+ name
593
+ email
594
+ }
595
+ }
596
+ ```
597
+
598
+ ### Custom Order Types
599
+
600
+ `orm.order_type(Model)` works the same way. `auto` fields get the standard `Ordering` enum. Methods decorated with `@order_field` receive a `value` of type `Ordering` (ASC, DESC, etc.) and return the modified query:
601
+
602
+ ```python
603
+ from strawberry_orm import order_field
604
+ from strawberry_orm.types import Ordering
605
+
606
+ @orm.order_type(User)
607
+ class UserOrder:
608
+ name: auto # standard Ordering (ASC/DESC/...)
609
+
610
+ @order_field
611
+ def post_count(self, value: Ordering, query):
612
+ """Order users by how many posts they have."""
613
+ from sqlalchemy import func
614
+ query = query.outerjoin(Post, Post.author_id == User.id).group_by(User.id)
615
+ col = func.count(Post.id)
616
+ if "DESC" in value.value:
617
+ return query.order_by(col.desc())
618
+ return query.order_by(col.asc())
619
+ ```
620
+
621
+ The generated GraphQL input:
622
+
623
+ ```graphql
624
+ input UserOrder @oneOf {
625
+ field: UserOrderField # auto-generated
626
+ object: UserOrderObject # auto-generated (if relations exist)
627
+ postCount: Ordering # custom
628
+ }
629
+ ```
630
+
631
+ Custom and standard orders compose naturally in the order list:
632
+
633
+ ```graphql
634
+ {
635
+ users(order: [
636
+ { postCount: DESC },
637
+ { field: { name: ASC } }
638
+ ]) {
639
+ name
640
+ }
641
+ }
642
+ ```
643
+
644
+ ### Using Custom Types
645
+
646
+ Custom filter and order types are used exactly like auto-generated ones:
647
+
648
+ ```python
649
+ @orm.type(User, filters=UserFilter, order=UserOrder)
650
+ class UserType:
651
+ id: auto
652
+ name: auto
653
+ email: auto
654
+
655
+ @strawberry.type
656
+ class Query:
657
+ @orm.field()
658
+ def users(self) -> list[UserType]:
659
+ return orm.get_default_queryset(User)
660
+ ```
661
+
662
+ They also work with Relay connections and `orm.connection()`.
663
+
664
+ ### Backend-Specific Examples
665
+
666
+ The query manipulation inside `@filter_field` and `@order_field` methods is backend-specific since it operates on native query objects. Here are equivalent examples for each backend:
667
+
668
+ <details>
669
+ <summary>Django</summary>
670
+
671
+ ```python
672
+ from django.db.models import Q, Count, F
673
+
674
+ @orm.filter_type(User)
675
+ class UserFilter:
676
+ name: auto
677
+
678
+ @filter_field
679
+ def search(self, value: str, query):
680
+ return query.filter(Q(name__icontains=value) | Q(email__icontains=value))
681
+
682
+ @orm.order_type(User)
683
+ class UserOrder:
684
+ name: auto
685
+
686
+ @order_field
687
+ def post_count(self, value: Ordering, query):
688
+ query = query.annotate(_post_count=Count("posts"))
689
+ dir_value = value.value
690
+ if dir_value.startswith("DESC"):
691
+ return query.order_by(F("_post_count").desc())
692
+ return query.order_by(F("_post_count").asc())
693
+ ```
694
+
695
+ </details>
696
+
697
+ <details>
698
+ <summary>Tortoise</summary>
699
+
700
+ ```python
701
+ from tortoise.queryset import Q
702
+ from tortoise.functions import Count
703
+
704
+ @orm.filter_type(User)
705
+ class UserFilter:
706
+ name: auto
707
+
708
+ @filter_field
709
+ def search(self, value: str, query):
710
+ return query.filter(Q(name__icontains=value) | Q(email__icontains=value))
711
+
712
+ @orm.order_type(User)
713
+ class UserOrder:
714
+ name: auto
715
+
716
+ @order_field
717
+ def post_count(self, value: Ordering, query):
718
+ query = query.annotate(_post_count=Count("posts"))
719
+ if value.value.startswith("DESC"):
720
+ return query.order_by("-_post_count")
721
+ return query.order_by("_post_count")
722
+ ```
723
+
724
+ </details>
725
+
726
+ ### Combining with `orm.filter()` / `orm.order()`
727
+
728
+ `orm.filter()` and `orm.order()` remain available for fully auto-generated types. Use `orm.filter_type()` and `orm.order_type()` only when you need custom logic. The types produced by both APIs are interchangeable in all contexts — `orm.type(Model, filters=..., order=...)`, `orm.field(filters=..., order=...)`, and `orm.connection()`.
729
+
730
+ ---
731
+
520
732
  ## Mutations
521
733
 
522
734
  Write plain `@strawberry.mutation` resolvers and use `strawberry-orm` for generated input types:
@@ -857,7 +1069,7 @@ orm = StrawberryORM(
857
1069
 
858
1070
  ## Public Exports
859
1071
 
860
- `StrawberryORM`, `auto`, `make_field`, `make_ref_type`, `Ordering`, `FieldDefinition`, `FieldHints`, `OptimizerExtension`, `OptimizerStore`, `UNSET`, and the built-in lookup input classes from `strawberry_orm.filters`.
1072
+ `StrawberryORM`, `auto`, `make_field`, `make_ref_type`, `Ordering`, `FieldDefinition`, `FieldHints`, `OptimizerExtension`, `OptimizerStore`, `UNSET`, `filter_field`, `order_field`, and the built-in lookup input classes from `strawberry_orm.filters`.
861
1073
 
862
1074
  ## License
863
1075
 
@@ -11,6 +11,7 @@ Backend-agnostic schema generation for [Strawberry GraphQL](https://strawberry.r
11
11
  - [Backends](#backends)
12
12
  - [Defining Types](#defining-types)
13
13
  - [Filters and Ordering](#filters-and-ordering)
14
+ - [Custom Filters and Ordering](#custom-filters-and-ordering)
14
15
  - [Mutations](#mutations)
15
16
  - [Relay Integration](#relay-integration)
16
17
  - [Query Optimization](#query-optimization)
@@ -94,7 +95,7 @@ class PostType:
94
95
 
95
96
  CreatePostInput = orm.input(Post, include=["title", "body", "author_id"])
96
97
  CreateTagInput = orm.input(Tag, include=["name"])
97
- TagRef = orm.ref(Tag, create=CreateTagInput, delete=True)
98
+ TagRef = orm.ref(Tag, create=CreateTagInput, unlink=True, delete=True)
98
99
 
99
100
  @strawberry.type
100
101
  class Mutation:
@@ -482,6 +483,217 @@ Registration order matters: define related orders *before* the parent (e.g. `orm
482
483
 
483
484
  ---
484
485
 
486
+ ## Custom Filters and Ordering
487
+
488
+ `orm.filter()` and `orm.order()` auto-generate types from model introspection. When you need filter logic that goes beyond column lookups — full-text search across multiple fields, subquery-based conditions, or ordering by computed values — use `orm.filter_type()` and `orm.order_type()` with the `@filter_field` and `@order_field` decorators.
489
+
490
+ ### Custom Filter Types
491
+
492
+ `orm.filter_type(Model)` is a class decorator. Annotate fields with `auto` for standard lookups (identical to what `orm.filter()` generates). Add methods decorated with `@filter_field` for custom logic:
493
+
494
+ ```python
495
+ from strawberry_orm import StrawberryORM, filter_field, auto
496
+
497
+ orm = StrawberryORM("sqlalchemy", dialect="postgresql", session_getter=...)
498
+
499
+ @orm.filter_type(User)
500
+ class UserFilter:
501
+ name: auto # standard StringLookup
502
+ email: auto # standard StringLookup
503
+
504
+ @filter_field
505
+ def search(self, value: str, query):
506
+ """Full-text search across name and email."""
507
+ from sqlalchemy import or_
508
+ return query.where(
509
+ or_(User.name.ilike(f"%{value}%"), User.email.ilike(f"%{value}%"))
510
+ )
511
+
512
+ @filter_field
513
+ def has_posts(self, value: bool, query):
514
+ """Filter users who have (or lack) any posts."""
515
+ from sqlalchemy import func, select
516
+ subq = (
517
+ select(func.count(Post.id))
518
+ .where(Post.author_id == User.id)
519
+ .correlate(User)
520
+ .scalar_subquery()
521
+ )
522
+ if value:
523
+ return query.where(subq > 0)
524
+ return query.where(subq == 0)
525
+ ```
526
+
527
+ Each `@filter_field` method must:
528
+
529
+ - Have a `value` parameter with a **type annotation** — this becomes the GraphQL input type for the field.
530
+ - Have a `query` parameter — receives the backend's native query object (Django `QuerySet`, SQLAlchemy `Select`, or Tortoise `QuerySet`).
531
+ - Return the modified query.
532
+ - Optionally accept an `info` parameter to receive the Strawberry `Info` context.
533
+
534
+ The generated GraphQL input places custom fields as top-level keys alongside `field`, `object`, `all`, `any`, `not`, and `oneOf`:
535
+
536
+ ```graphql
537
+ input UserFilter @oneOf {
538
+ field: UserField # auto-generated scalar lookups
539
+ object: UserObject # auto-generated relation lookups (if any)
540
+ search: String # custom
541
+ hasPosts: Boolean # custom
542
+ all: [UserFilter!]
543
+ any: [UserFilter!]
544
+ not: UserFilter
545
+ oneOf: [UserFilter!]
546
+ }
547
+ ```
548
+
549
+ Since filters are `@oneOf`, combine custom filters with standard lookups using `all` or `any`:
550
+
551
+ ```graphql
552
+ {
553
+ users(filter: { all: [
554
+ { search: "john" },
555
+ { field: { email: { contains: "example.com" } } }
556
+ ] }) {
557
+ name
558
+ email
559
+ }
560
+ }
561
+ ```
562
+
563
+ ### Custom Order Types
564
+
565
+ `orm.order_type(Model)` works the same way. `auto` fields get the standard `Ordering` enum. Methods decorated with `@order_field` receive a `value` of type `Ordering` (ASC, DESC, etc.) and return the modified query:
566
+
567
+ ```python
568
+ from strawberry_orm import order_field
569
+ from strawberry_orm.types import Ordering
570
+
571
+ @orm.order_type(User)
572
+ class UserOrder:
573
+ name: auto # standard Ordering (ASC/DESC/...)
574
+
575
+ @order_field
576
+ def post_count(self, value: Ordering, query):
577
+ """Order users by how many posts they have."""
578
+ from sqlalchemy import func
579
+ query = query.outerjoin(Post, Post.author_id == User.id).group_by(User.id)
580
+ col = func.count(Post.id)
581
+ if "DESC" in value.value:
582
+ return query.order_by(col.desc())
583
+ return query.order_by(col.asc())
584
+ ```
585
+
586
+ The generated GraphQL input:
587
+
588
+ ```graphql
589
+ input UserOrder @oneOf {
590
+ field: UserOrderField # auto-generated
591
+ object: UserOrderObject # auto-generated (if relations exist)
592
+ postCount: Ordering # custom
593
+ }
594
+ ```
595
+
596
+ Custom and standard orders compose naturally in the order list:
597
+
598
+ ```graphql
599
+ {
600
+ users(order: [
601
+ { postCount: DESC },
602
+ { field: { name: ASC } }
603
+ ]) {
604
+ name
605
+ }
606
+ }
607
+ ```
608
+
609
+ ### Using Custom Types
610
+
611
+ Custom filter and order types are used exactly like auto-generated ones:
612
+
613
+ ```python
614
+ @orm.type(User, filters=UserFilter, order=UserOrder)
615
+ class UserType:
616
+ id: auto
617
+ name: auto
618
+ email: auto
619
+
620
+ @strawberry.type
621
+ class Query:
622
+ @orm.field()
623
+ def users(self) -> list[UserType]:
624
+ return orm.get_default_queryset(User)
625
+ ```
626
+
627
+ They also work with Relay connections and `orm.connection()`.
628
+
629
+ ### Backend-Specific Examples
630
+
631
+ The query manipulation inside `@filter_field` and `@order_field` methods is backend-specific since it operates on native query objects. Here are equivalent examples for each backend:
632
+
633
+ <details>
634
+ <summary>Django</summary>
635
+
636
+ ```python
637
+ from django.db.models import Q, Count, F
638
+
639
+ @orm.filter_type(User)
640
+ class UserFilter:
641
+ name: auto
642
+
643
+ @filter_field
644
+ def search(self, value: str, query):
645
+ return query.filter(Q(name__icontains=value) | Q(email__icontains=value))
646
+
647
+ @orm.order_type(User)
648
+ class UserOrder:
649
+ name: auto
650
+
651
+ @order_field
652
+ def post_count(self, value: Ordering, query):
653
+ query = query.annotate(_post_count=Count("posts"))
654
+ dir_value = value.value
655
+ if dir_value.startswith("DESC"):
656
+ return query.order_by(F("_post_count").desc())
657
+ return query.order_by(F("_post_count").asc())
658
+ ```
659
+
660
+ </details>
661
+
662
+ <details>
663
+ <summary>Tortoise</summary>
664
+
665
+ ```python
666
+ from tortoise.queryset import Q
667
+ from tortoise.functions import Count
668
+
669
+ @orm.filter_type(User)
670
+ class UserFilter:
671
+ name: auto
672
+
673
+ @filter_field
674
+ def search(self, value: str, query):
675
+ return query.filter(Q(name__icontains=value) | Q(email__icontains=value))
676
+
677
+ @orm.order_type(User)
678
+ class UserOrder:
679
+ name: auto
680
+
681
+ @order_field
682
+ def post_count(self, value: Ordering, query):
683
+ query = query.annotate(_post_count=Count("posts"))
684
+ if value.value.startswith("DESC"):
685
+ return query.order_by("-_post_count")
686
+ return query.order_by("_post_count")
687
+ ```
688
+
689
+ </details>
690
+
691
+ ### Combining with `orm.filter()` / `orm.order()`
692
+
693
+ `orm.filter()` and `orm.order()` remain available for fully auto-generated types. Use `orm.filter_type()` and `orm.order_type()` only when you need custom logic. The types produced by both APIs are interchangeable in all contexts — `orm.type(Model, filters=..., order=...)`, `orm.field(filters=..., order=...)`, and `orm.connection()`.
694
+
695
+ ---
696
+
485
697
  ## Mutations
486
698
 
487
699
  Write plain `@strawberry.mutation` resolvers and use `strawberry-orm` for generated input types:
@@ -822,7 +1034,7 @@ orm = StrawberryORM(
822
1034
 
823
1035
  ## Public Exports
824
1036
 
825
- `StrawberryORM`, `auto`, `make_field`, `make_ref_type`, `Ordering`, `FieldDefinition`, `FieldHints`, `OptimizerExtension`, `OptimizerStore`, `UNSET`, and the built-in lookup input classes from `strawberry_orm.filters`.
1037
+ `StrawberryORM`, `auto`, `make_field`, `make_ref_type`, `Ordering`, `FieldDefinition`, `FieldHints`, `OptimizerExtension`, `OptimizerStore`, `UNSET`, `filter_field`, `order_field`, and the built-in lookup input classes from `strawberry_orm.filters`.
826
1038
 
827
1039
  ## License
828
1040
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "strawberry-orm"
3
- version = "0.6.0"
3
+ version = "0.7.0"
4
4
  description = "Unified, backend-agnostic ORM abstraction for Strawberry GraphQL"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -67,7 +67,8 @@ select = [
67
67
  "SIM", # flake8-simplify
68
68
  ]
69
69
  ignore = [
70
- "E501", # line too long (handled by formatter)
70
+ "E501", # line too long (handled by formatter)
71
+ "UP046", # Generic[M] needed for runtime introspection via get_original_bases
71
72
  ]
72
73
 
73
74
  [tool.ruff.lint.isort]
@@ -12,9 +12,13 @@ from strawberry_orm.filters import (
12
12
  StringLookup,
13
13
  StringLookupNoRegex,
14
14
  TimeComparisonLookup,
15
+ filter_field,
16
+ order_field,
15
17
  )
16
18
  from strawberry_orm.mutations import make_ref_type
17
19
  from strawberry_orm.optimizer import FieldHints, OptimizerExtension, OptimizerStore
20
+ from strawberry_orm.policy import MutationPolicy
21
+ from strawberry_orm.repo import AbstractRepo
18
22
  from strawberry_orm.types import (
19
23
  UNSET,
20
24
  FieldDefinition,
@@ -25,6 +29,7 @@ from strawberry_orm.types import (
25
29
  )
26
30
 
27
31
  __all__ = [
32
+ "AbstractRepo",
28
33
  "BooleanLookup",
29
34
  "DateComparisonLookup",
30
35
  "DateTimeComparisonLookup",
@@ -33,6 +38,7 @@ __all__ = [
33
38
  "FloatComparisonLookup",
34
39
  "IDLookup",
35
40
  "IntComparisonLookup",
41
+ "MutationPolicy",
36
42
  "OperationInfo",
37
43
  "OperationMessage",
38
44
  "OptimizerExtension",
@@ -44,6 +50,8 @@ __all__ = [
44
50
  "TimeComparisonLookup",
45
51
  "UNSET",
46
52
  "auto",
53
+ "filter_field",
47
54
  "make_field",
48
55
  "make_ref_type",
56
+ "order_field",
49
57
  ]
@@ -3,9 +3,10 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import asyncio
6
+ from collections.abc import Awaitable, Callable
6
7
  from functools import partial
7
8
  from inspect import isawaitable
8
- from typing import Any, Awaitable, Callable, TypeVar
9
+ from typing import Any, TypeVar
9
10
 
10
11
  T = TypeVar("T")
11
12