exdrf 0.0.1.dev0__py3-none-any.whl

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 (57) hide show
  1. exdrf/__init__.py +0 -0
  2. exdrf/__version__.py +24 -0
  3. exdrf/api.py +51 -0
  4. exdrf/constants.py +30 -0
  5. exdrf/dataset.py +197 -0
  6. exdrf/field.py +554 -0
  7. exdrf/field_types/__init__.py +0 -0
  8. exdrf/field_types/api.py +78 -0
  9. exdrf/field_types/blob_field.py +44 -0
  10. exdrf/field_types/bool_field.py +47 -0
  11. exdrf/field_types/date_field.py +49 -0
  12. exdrf/field_types/date_time.py +52 -0
  13. exdrf/field_types/dur_field.py +44 -0
  14. exdrf/field_types/enum_field.py +41 -0
  15. exdrf/field_types/filter_field.py +11 -0
  16. exdrf/field_types/float_field.py +85 -0
  17. exdrf/field_types/float_list.py +18 -0
  18. exdrf/field_types/formatted.py +39 -0
  19. exdrf/field_types/int_field.py +70 -0
  20. exdrf/field_types/int_list.py +18 -0
  21. exdrf/field_types/ref_base.py +105 -0
  22. exdrf/field_types/ref_m2m.py +39 -0
  23. exdrf/field_types/ref_m2o.py +23 -0
  24. exdrf/field_types/ref_o2m.py +36 -0
  25. exdrf/field_types/ref_o2o.py +32 -0
  26. exdrf/field_types/sort_field.py +18 -0
  27. exdrf/field_types/str_field.py +77 -0
  28. exdrf/field_types/str_list.py +18 -0
  29. exdrf/field_types/time_field.py +49 -0
  30. exdrf/filter.py +653 -0
  31. exdrf/filter_dsl.py +950 -0
  32. exdrf/filter_op_catalog.py +222 -0
  33. exdrf/label_dsl.py +691 -0
  34. exdrf/moment.py +496 -0
  35. exdrf/py.typed +0 -0
  36. exdrf/py_support.py +21 -0
  37. exdrf/resource.py +901 -0
  38. exdrf/sa_fi_item.py +69 -0
  39. exdrf/sa_filter_op.py +324 -0
  40. exdrf/utils.py +17 -0
  41. exdrf/validator.py +45 -0
  42. exdrf/var_bag.py +328 -0
  43. exdrf/visitor.py +58 -0
  44. exdrf-0.0.1.dev0.dist-info/METADATA +42 -0
  45. exdrf-0.0.1.dev0.dist-info/RECORD +57 -0
  46. exdrf-0.0.1.dev0.dist-info/WHEEL +5 -0
  47. exdrf-0.0.1.dev0.dist-info/top_level.txt +3 -0
  48. exdrf_tests/__init__.py +0 -0
  49. exdrf_tests/test_dataset.py +422 -0
  50. exdrf_tests/test_field.py +109 -0
  51. exdrf_tests/test_filter.py +425 -0
  52. exdrf_tests/test_filter_dsl.py +556 -0
  53. exdrf_tests/test_label_dsl.py +234 -0
  54. exdrf_tests/test_resource.py +107 -0
  55. exdrf_tests/test_utils.py +43 -0
  56. exdrf_tests/test_visitor.py +31 -0
  57. exdrf_tests/var_bag_test.py +502 -0
@@ -0,0 +1,47 @@
1
+ from typing import Any, Optional
2
+
3
+ from attrs import define, field
4
+
5
+ from exdrf.constants import FIELD_TYPE_BOOL
6
+ from exdrf.field import ExField, FieldInfo
7
+
8
+
9
+ @define
10
+ class BoolField(ExField):
11
+ """A field that stores boolean values.
12
+
13
+ This field is not expected to resize when in list view mode.
14
+
15
+ Attributes:
16
+ true_str: The string representation of the boolean value `True`.
17
+ false_str: The string representation of the boolean value `False`.
18
+ """
19
+
20
+ type_name: str = field(default=FIELD_TYPE_BOOL)
21
+ resizable: bool = field(default=False)
22
+
23
+ true_str: str = field(default="True")
24
+ false_str: str = field(default="False")
25
+
26
+ def __repr__(self) -> str:
27
+ return f"BoolF({self.resource.name}.{self.name})"
28
+
29
+ def field_properties(self, explicit: bool = False) -> dict[str, Any]:
30
+ result = super().field_properties(explicit)
31
+ if self.true_str or explicit:
32
+ result["true_str"] = self.true_str
33
+ if self.false_str or explicit:
34
+ result["false_str"] = self.false_str
35
+ return result
36
+
37
+
38
+ class BoolInfo(FieldInfo):
39
+ """Parser for information about a boolean field.
40
+
41
+ Attributes:
42
+ true_str: The string representation of the boolean value `True`.
43
+ false_str: The string representation of the boolean value `False`.
44
+ """
45
+
46
+ true_str: Optional[str] = None
47
+ false_str: Optional[str] = None
@@ -0,0 +1,49 @@
1
+ from datetime import date
2
+ from typing import Any, Optional
3
+
4
+ from attrs import define, field
5
+
6
+ from exdrf.constants import FIELD_TYPE_DATE
7
+ from exdrf.field import ExField, FieldInfo
8
+
9
+
10
+ @define
11
+ class DateField(ExField):
12
+ """A field that stores moments in time.
13
+
14
+ Attributes:
15
+ min: The minimum date that can be stored in the field.
16
+ max: The maximum date that can be stored in the field.
17
+ format: The format of the date string.
18
+ """
19
+
20
+ type_name: str = field(default=FIELD_TYPE_DATE)
21
+
22
+ min: date = field(default=None)
23
+ max: date = field(default=None)
24
+ format: str = field(default="DD-MM-YYYY")
25
+
26
+ def __repr__(self) -> str:
27
+ return f"DaTiF({self.resource.name}.{self.name})"
28
+
29
+ def field_properties(self, explicit: bool = False) -> dict[str, Any]:
30
+ result = super().field_properties(explicit)
31
+ if self.min or explicit:
32
+ result["min"] = self.min
33
+ if self.max or explicit:
34
+ result["max"] = self.max
35
+ if self.format or explicit:
36
+ result["format"] = self.format
37
+ return result
38
+
39
+
40
+ class DateInfo(FieldInfo):
41
+ """Parser for information about a date-time field.
42
+
43
+ Attributes:
44
+ min: The minimum date that can be stored in the field.
45
+ max: The maximum date that can be stored in the field.
46
+ """
47
+
48
+ min: Optional[date] = None
49
+ max: Optional[date] = None
@@ -0,0 +1,52 @@
1
+ from datetime import datetime
2
+ from typing import Any, Optional
3
+
4
+ from attrs import define, field
5
+
6
+ from exdrf.constants import FIELD_TYPE_DT
7
+ from exdrf.field import ExField, FieldInfo
8
+
9
+ # Sentinel date-time value used to represent an "unknown" date.
10
+ UNKNOWN_DATETIME = datetime(1000, 2, 3, 4, 5, 6, 7)
11
+
12
+
13
+ @define
14
+ class DateTimeField(ExField):
15
+ """A field that stores moments in time.
16
+
17
+ Attributes:
18
+ min: The minimum date-time that can be stored in the field.
19
+ max: The maximum date-time that can be stored in the field.
20
+ format: The format of the date-time string.
21
+ """
22
+
23
+ type_name: str = field(default=FIELD_TYPE_DT)
24
+
25
+ min: datetime = field(default=None)
26
+ max: datetime = field(default=None)
27
+ format: str = field(default="DD-MM-YYYY HH:mm:ss")
28
+
29
+ def __repr__(self) -> str:
30
+ return f"DaTiF({self.resource.name}.{self.name})"
31
+
32
+ def field_properties(self, explicit: bool = False) -> dict[str, Any]:
33
+ result = super().field_properties(explicit)
34
+ if self.min or explicit:
35
+ result["min"] = self.min
36
+ if self.max or explicit:
37
+ result["max"] = self.max
38
+ if self.format or explicit:
39
+ result["format"] = self.format
40
+ return result
41
+
42
+
43
+ class DateTimeInfo(FieldInfo):
44
+ """Parser for information about a date-time field.
45
+
46
+ Attributes:
47
+ min: The minimum date-time that can be stored in the field.
48
+ max: The maximum date-time that can be stored in the field.
49
+ """
50
+
51
+ min: Optional[datetime] = None
52
+ max: Optional[datetime] = None
@@ -0,0 +1,44 @@
1
+ from typing import Any, Optional
2
+
3
+ from attrs import define, field
4
+
5
+ from exdrf.constants import FIELD_TYPE_DURATION
6
+ from exdrf.field import ExField, FieldInfo
7
+
8
+
9
+ @define
10
+ class DurationField(ExField):
11
+ """A field that stores the duration of time.
12
+
13
+ Attributes:
14
+ min: The minimum duration that can be stored in the field.
15
+ max: The maximum duration that can be stored in the field.
16
+ """
17
+
18
+ type_name: str = field(default=FIELD_TYPE_DURATION)
19
+
20
+ min: float = field(default=None)
21
+ max: float = field(default=None)
22
+
23
+ def __repr__(self) -> str:
24
+ return f"DaTiF({self.resource.name}.{self.name})"
25
+
26
+ def field_properties(self, explicit: bool = False) -> dict[str, Any]:
27
+ result = super().field_properties(explicit)
28
+ if self.min or explicit:
29
+ result["min"] = self.min
30
+ if self.max or explicit:
31
+ result["max"] = self.max
32
+ return result
33
+
34
+
35
+ class DurationInfo(FieldInfo):
36
+ """Parser for information about a time duration field.
37
+
38
+ Attributes:
39
+ min: The minimum duration that can be stored in the field.
40
+ max: The maximum duration that can be stored in the field.
41
+ """
42
+
43
+ min: Optional[float] = None
44
+ max: Optional[float] = None
@@ -0,0 +1,41 @@
1
+ from typing import Any, List, Tuple
2
+
3
+ from attrs import define, field
4
+ from pydantic import Field
5
+
6
+ from exdrf.constants import FIELD_TYPE_ENUM
7
+ from exdrf.field import ExField, FieldInfo
8
+
9
+
10
+ @define
11
+ class EnumField(ExField):
12
+ """A field that stores moments in time.
13
+
14
+ Attributes:
15
+ enum_values: The list of possible values for the field. The first
16
+ element of the tuple is the value, the second element is the display
17
+ name.
18
+ """
19
+
20
+ type_name: str = field(default=FIELD_TYPE_ENUM)
21
+
22
+ enum_values: List[Tuple[str, str]] = field(factory=list)
23
+
24
+ def __repr__(self) -> str:
25
+ return f"EnumF({self.resource.name}.{self.name})"
26
+
27
+ def field_properties(self, explicit: bool = False) -> dict[str, Any]:
28
+ result = super().field_properties(explicit)
29
+ if self.enum_values or explicit:
30
+ result["enum_values"] = self.enum_values
31
+ return result
32
+
33
+
34
+ class EnumInfo(FieldInfo):
35
+ """Parser for information about an enum field.
36
+
37
+ Attributes:
38
+ enum_values: The list of possible values for the field.
39
+ """
40
+
41
+ enum_values: List[str] = Field(default_factory=list)
@@ -0,0 +1,11 @@
1
+ from attrs import define, field
2
+
3
+ from exdrf.constants import FIELD_TYPE_FILTER
4
+ from exdrf.field import ExField
5
+
6
+
7
+ @define
8
+ class FilterField(ExField):
9
+ """Field for filtering results."""
10
+
11
+ type_name: str = field(default=FIELD_TYPE_FILTER)
@@ -0,0 +1,85 @@
1
+ from typing import Any, List, Optional, Tuple
2
+
3
+ from attrs import define, field
4
+ from pydantic import Field, field_validator
5
+
6
+ from exdrf.constants import FIELD_TYPE_FLOAT
7
+ from exdrf.field import ExField, FieldInfo
8
+
9
+
10
+ @define
11
+ class FloatField(ExField):
12
+ """A field that stores real numbers.
13
+
14
+ Attributes:
15
+ min: The minimum value that can be stored in the field (inclusive).
16
+ max: The maximum value that can be stored in the field (inclusive).
17
+ precision: The number of digits that can be stored in the field.
18
+ scale: The number of digits to the right of the decimal point.
19
+ unit: The unit of measurement for the field.
20
+ unit_symbol: The symbol for the unit of measurement.
21
+ enum_values: The list of predefined (named) values for the field. The
22
+ first element of the tuple is the value, the second element is the
23
+ display name.
24
+ """
25
+
26
+ type_name: str = field(default=FIELD_TYPE_FLOAT)
27
+
28
+ min: float = field(default=None)
29
+ max: float = field(default=None)
30
+ precision: int = field(default=2)
31
+ scale: int = field(default=1)
32
+ unit: str = field(default=None)
33
+ unit_symbol: str = field(default=None)
34
+ enum_values: List[Tuple[float, str]] = field(factory=list)
35
+
36
+ def __repr__(self) -> str:
37
+ return f"FloatF({self.resource.name}.{self.name})"
38
+
39
+ def field_properties(self, explicit: bool = False) -> dict[str, Any]:
40
+ result = super().field_properties(explicit)
41
+ if self.min is not None or explicit:
42
+ result["min"] = self.min
43
+ if self.max is not None or explicit:
44
+ result["max"] = self.max
45
+ if self.precision is not None or explicit:
46
+ result["precision"] = self.precision
47
+ if self.scale is not None or explicit:
48
+ result["scale"] = self.scale
49
+ if self.unit or explicit:
50
+ result["unit"] = self.unit
51
+ if self.unit_symbol or explicit:
52
+ result["unit_symbol"] = self.unit_symbol
53
+ if self.enum_values or explicit:
54
+ result["enum_values"] = self.enum_values
55
+ return result
56
+
57
+
58
+ class FloatInfo(FieldInfo):
59
+ """Parser for information about a real-number field.
60
+
61
+ Attributes:
62
+ min: The minimum value that can be stored in the field.
63
+ max: The maximum value that can be stored in the field.
64
+ precision: The number of digits that can be stored in the field.
65
+ scale: The number of digits to the right of the decimal point.
66
+ unit: The unit of measurement for the field.
67
+ unit_symbol: The symbol for the unit of measurement.
68
+ """
69
+
70
+ min: Optional[float] = None
71
+ max: Optional[float] = None
72
+ precision: Optional[int] = None
73
+ scale: Optional[int] = None
74
+ unit: Optional[str] = None
75
+ unit_symbol: Optional[str] = None
76
+ enum_values: List[Tuple[float, str]] = Field(default_factory=list)
77
+
78
+ @field_validator("enum_values", mode="before")
79
+ @classmethod
80
+ def validate_enum_values(cls, v):
81
+ """Validate the enum values.
82
+
83
+ Accepts either a list of (int, str) tuples or an Enum class.
84
+ """
85
+ return cls.validate_enum_with_type(v, float)
@@ -0,0 +1,18 @@
1
+ from attrs import define, field
2
+
3
+ from exdrf.constants import FIELD_TYPE_FLOAT_LIST
4
+ from exdrf.field_types.float_field import FloatField, FloatInfo
5
+
6
+
7
+ @define
8
+ class FloatListField(FloatField):
9
+ """A field that stores list of real numbers."""
10
+
11
+ type_name: str = field(default=FIELD_TYPE_FLOAT_LIST)
12
+
13
+ def __repr__(self) -> str:
14
+ return f"FloatListF({self.resource.name}.{self.name})"
15
+
16
+
17
+ class FloatListInfo(FloatInfo):
18
+ """Parser for information about a real-numbers list field."""
@@ -0,0 +1,39 @@
1
+ from typing import Any, Literal, Optional
2
+
3
+ from attrs import define, field
4
+
5
+ from exdrf.constants import FIELD_TYPE_FORMATTED
6
+ from exdrf.field import FieldInfo
7
+ from exdrf.field_types.str_field import StrField
8
+
9
+
10
+ @define
11
+ class FormattedField(StrField):
12
+ """A field that stores strings that contain both text and markup.
13
+
14
+ Attributes:
15
+ format: The format of the string. Can be "json", "html", or "xml".
16
+ """
17
+
18
+ type_name: str = field(default=FIELD_TYPE_FORMATTED)
19
+
20
+ format: Literal["json", "html", "xml"] = field(default="json")
21
+
22
+ def __repr__(self) -> str:
23
+ return f"StrF({self.resource.name}.{self.name})"
24
+
25
+ def field_properties(self, explicit: bool = False) -> dict[str, Any]:
26
+ result = super().field_properties(explicit)
27
+ if self.format or explicit:
28
+ result["format"] = self.format
29
+ return result
30
+
31
+
32
+ class FormattedInfo(FieldInfo):
33
+ """Parser for information about a string field.
34
+
35
+ Attributes:
36
+ format: The format of the string. Can be "json", "html", or "xml".
37
+ """
38
+
39
+ format: Optional[Literal["json", "html", "xml"]] = None
@@ -0,0 +1,70 @@
1
+ from typing import Any, List, Optional, Tuple
2
+
3
+ from attrs import define, field
4
+ from pydantic import Field, field_validator
5
+
6
+ from exdrf.constants import FIELD_TYPE_INTEGER
7
+ from exdrf.field import ExField, FieldInfo
8
+
9
+
10
+ @define
11
+ class IntField(ExField):
12
+ """A field that stores integers.
13
+
14
+ Attributes:
15
+ min: The minimum integer that can be stored in the field (inclusive).
16
+ max: The maximum integer that can be stored in the field (inclusive).
17
+ unit: The unit of measurement for the field.
18
+ unit_symbol: The symbol for the unit of measurement.
19
+ """
20
+
21
+ type_name: str = field(default=FIELD_TYPE_INTEGER)
22
+
23
+ min: int = field(default=None)
24
+ max: int = field(default=None)
25
+ unit: str = field(default=None)
26
+ unit_symbol: str = field(default=None)
27
+ enum_values: List[Tuple[int, str]] = field(factory=list)
28
+
29
+ def __repr__(self) -> str:
30
+ return f"IntF({self.resource.name}.{self.name})"
31
+
32
+ def field_properties(self, explicit: bool = False) -> dict[str, Any]:
33
+ result = super().field_properties(explicit)
34
+ if self.min is not None or explicit:
35
+ result["min"] = self.min
36
+ if self.max is not None or explicit:
37
+ result["max"] = self.max
38
+ if self.unit or explicit:
39
+ result["unit"] = self.unit
40
+ if self.unit_symbol or explicit:
41
+ result["unit_symbol"] = self.unit_symbol
42
+ if self.enum_values or explicit:
43
+ result["enum_values"] = self.enum_values
44
+ return result
45
+
46
+
47
+ class IntInfo(FieldInfo):
48
+ """Parser for information about an integer field.
49
+
50
+ Attributes:
51
+ min: The minimum integer that can be stored in the field.
52
+ max: The maximum integer that can be stored in the field.
53
+ unit: The unit of measurement for the field.
54
+ unit_symbol: The symbol for the unit of measurement.
55
+ """
56
+
57
+ min: Optional[int] = None
58
+ max: Optional[int] = None
59
+ unit: Optional[str] = None
60
+ unit_symbol: Optional[str] = None
61
+ enum_values: List[Tuple[int, str]] = Field(default_factory=list)
62
+
63
+ @field_validator("enum_values", mode="before")
64
+ @classmethod
65
+ def validate_enum_values(cls, v):
66
+ """Validate the enum values.
67
+
68
+ Accepts either a list of (int, str) tuples or an Enum class.
69
+ """
70
+ return cls.validate_enum_with_type(v, int)
@@ -0,0 +1,18 @@
1
+ from attrs import define, field
2
+
3
+ from exdrf.constants import FIELD_TYPE_INT_LIST
4
+ from exdrf.field_types.int_field import IntField, IntInfo
5
+
6
+
7
+ @define
8
+ class IntListField(IntField):
9
+ """A field that stores a list of integers."""
10
+
11
+ type_name: str = field(default=FIELD_TYPE_INT_LIST)
12
+
13
+ def __repr__(self) -> str:
14
+ return f"IntListF({self.resource.name}.{self.name})"
15
+
16
+
17
+ class IntListInfo(IntInfo):
18
+ """Parser for information about an integer-list field."""
@@ -0,0 +1,105 @@
1
+ from typing import Any, List, Optional, Tuple
2
+
3
+ from attrs import define, field
4
+ from pydantic import Field, field_validator
5
+
6
+ from exdrf.constants import RelType
7
+ from exdrf.field import ExField, FieldInfo
8
+ from exdrf.resource import ExResource
9
+
10
+
11
+ @define
12
+ class RefBaseField(ExField):
13
+ """Base class for resource relations.
14
+
15
+ Attributes:
16
+ ref: The related resource.
17
+ expect_lots: If true it indicates that the relation is expected to have
18
+ many items. This is only valid when the parent side is 'many'
19
+ (many-to-one or many-to-many relations from the parent's point of
20
+ view).
21
+ provides: indicates the concept that this field provides. This is
22
+ usually set at the resource level but can be overridden at the field
23
+ level.
24
+ depends_on: indicates the concepts that this field depends on. This is
25
+ usually set at the resource level but can be overridden at the field
26
+ level.
27
+ use_rel: When True on a non-bridge OneToMany relationship (from
28
+ SQLAlchemy ``relationship`` ``info``), Qt editor generation uses a
29
+ dedicated DrfRelated tab instead of an inline multi-select. Ignored
30
+ for other relation kinds.
31
+ """
32
+
33
+ ref: "ExResource" = field(default=None, repr=False)
34
+ expect_lots: bool = field(default=False)
35
+ provides: List[str] = field(factory=list)
36
+ depends_on: List[str] = field(factory=list)
37
+ use_rel: bool = field(default=False)
38
+
39
+ def field_properties(self, explicit: bool = False) -> dict[str, Any]:
40
+ result = super().field_properties(explicit)
41
+ result["ref"] = self.ref.name
42
+ if self.use_rel or explicit:
43
+ result["use_rel"] = self.use_rel
44
+ return result
45
+
46
+
47
+ class RelExtraInfo(FieldInfo):
48
+ """Parser for information about a related resource.
49
+
50
+ Attributes:
51
+ direction: The direction of the relationship. Can be "OneToMany",
52
+ "ManyToOne", "OneToOne", or "ManyToMany".
53
+ subordinate: If true it indicates that the child resource is parented
54
+ into current resource through this relation. Child resources get
55
+ deleted when the parent is deleted and they should not be
56
+ independently managed at the top level. Instead, the widget for
57
+ this relation in the parent resource will be adapted to show an
58
+ editable list of children where the children are added and removed.
59
+ This is only valid when the parent side is 'one' (one-to-many or
60
+ one-to-one relations from the parent's point of view).
61
+ expect_lots: If true it indicates that the relation is expected to have
62
+ many items. This is only valid when the parent side is 'many'
63
+ (many-to-one or many-to-many relations from the parent's point of
64
+ view).
65
+ provides: indicates the concept that this field provides. This is
66
+ usually set at the resource level but can be overridden at the field
67
+ level.
68
+ depends_on: indicates the concepts that this field depends on. This is
69
+ usually set at the resource level but can be overridden at the field
70
+ level.
71
+ bridge: Even if the relation type is declared as OneToMany, the
72
+ other side is a junction table with extra attributes. The value is
73
+ the name of the resource on the other side.
74
+ """
75
+
76
+ direction: Optional[RelType] = None
77
+ subordinate: Optional[bool] = None
78
+ expect_lots: Optional[bool] = False
79
+ provides: List[str] = Field(default_factory=list)
80
+ depends_on: List[Tuple[str, str]] = Field(default_factory=list)
81
+ bridge: Optional[str] = None
82
+
83
+ @field_validator("provides", mode="before")
84
+ @classmethod
85
+ def parse_provides(cls, v):
86
+ if v is None:
87
+ return []
88
+ if isinstance(v, str):
89
+ return [item.strip() for item in v.split(",") if item.strip()]
90
+ return v
91
+
92
+ @field_validator("depends_on", mode="before")
93
+ @classmethod
94
+ def parse_depends_on(cls, v):
95
+ if v is None:
96
+ return []
97
+ if isinstance(v, str):
98
+ result = []
99
+ for part in v.split(","):
100
+ if not part.strip():
101
+ continue
102
+ concept, target = part.strip().split(":", maxsplit=1)
103
+ result.append((concept.strip(), target.strip()))
104
+ return result
105
+ return v
@@ -0,0 +1,39 @@
1
+ from typing import TYPE_CHECKING, Any
2
+
3
+ from attrs import define, field
4
+
5
+ from exdrf.constants import FIELD_TYPE_REF_MANY_TO_MANY
6
+ from exdrf.field_types.ref_base import RefBaseField
7
+
8
+ if TYPE_CHECKING:
9
+ from exdrf.resource import ExResource # noqa: F401
10
+
11
+
12
+ @define
13
+ class RefManyToManyField(RefBaseField):
14
+ """This type of field is created by ManyToMany relations.
15
+
16
+ In this type of relation there are many items of the present resource that
17
+ are related to many items of the related resource. The relation is
18
+ implemented by a third resource that contains the foreign keys to both
19
+ resources and is referenced by the `ref_intermediate` attribute.
20
+
21
+ It is asserted that in this case the `is_list` attribute is set to `True`.
22
+
23
+ Attribute:
24
+ ref_intermediate: The intermediate resource that implements the
25
+ relation.
26
+ """
27
+
28
+ type_name: str = field(default=FIELD_TYPE_REF_MANY_TO_MANY)
29
+ is_list: bool = field(default=True)
30
+
31
+ ref_intermediate: "ExResource" = field(default=None, repr=False)
32
+
33
+ def field_properties(self, explicit: bool = False) -> dict[str, Any]:
34
+ result = super().field_properties(explicit)
35
+ result["ref_intermediate"] = self.ref_intermediate.name
36
+ return result
37
+
38
+ def __repr__(self) -> str:
39
+ return f"M2M({self.ref.name}, {self.ref_intermediate.name})"
@@ -0,0 +1,23 @@
1
+ from attrs import define, field
2
+
3
+ from exdrf.constants import FIELD_TYPE_REF_MANY_TO_ONE
4
+ from exdrf.field_types.ref_base import RefBaseField
5
+
6
+
7
+ @define
8
+ class RefManyToOneField(RefBaseField):
9
+ """This type of field is created by ManyToOne relations.
10
+
11
+ In this type of relation there are many items of the present
12
+ resource that are related to one item of the related resource. Expect
13
+ the foreign key to be in the *present* resource.
14
+
15
+ It is asserted that in this case the `is_list` attribute is set to
16
+ `False`.
17
+ """
18
+
19
+ type_name: str = field(default=FIELD_TYPE_REF_MANY_TO_ONE)
20
+ is_list: bool = field(default=False)
21
+
22
+ def __repr__(self) -> str:
23
+ return f"M2O({self.ref.name})"