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.
- exdrf/__init__.py +0 -0
- exdrf/__version__.py +24 -0
- exdrf/api.py +51 -0
- exdrf/constants.py +30 -0
- exdrf/dataset.py +197 -0
- exdrf/field.py +554 -0
- exdrf/field_types/__init__.py +0 -0
- exdrf/field_types/api.py +78 -0
- exdrf/field_types/blob_field.py +44 -0
- exdrf/field_types/bool_field.py +47 -0
- exdrf/field_types/date_field.py +49 -0
- exdrf/field_types/date_time.py +52 -0
- exdrf/field_types/dur_field.py +44 -0
- exdrf/field_types/enum_field.py +41 -0
- exdrf/field_types/filter_field.py +11 -0
- exdrf/field_types/float_field.py +85 -0
- exdrf/field_types/float_list.py +18 -0
- exdrf/field_types/formatted.py +39 -0
- exdrf/field_types/int_field.py +70 -0
- exdrf/field_types/int_list.py +18 -0
- exdrf/field_types/ref_base.py +105 -0
- exdrf/field_types/ref_m2m.py +39 -0
- exdrf/field_types/ref_m2o.py +23 -0
- exdrf/field_types/ref_o2m.py +36 -0
- exdrf/field_types/ref_o2o.py +32 -0
- exdrf/field_types/sort_field.py +18 -0
- exdrf/field_types/str_field.py +77 -0
- exdrf/field_types/str_list.py +18 -0
- exdrf/field_types/time_field.py +49 -0
- exdrf/filter.py +653 -0
- exdrf/filter_dsl.py +950 -0
- exdrf/filter_op_catalog.py +222 -0
- exdrf/label_dsl.py +691 -0
- exdrf/moment.py +496 -0
- exdrf/py.typed +0 -0
- exdrf/py_support.py +21 -0
- exdrf/resource.py +901 -0
- exdrf/sa_fi_item.py +69 -0
- exdrf/sa_filter_op.py +324 -0
- exdrf/utils.py +17 -0
- exdrf/validator.py +45 -0
- exdrf/var_bag.py +328 -0
- exdrf/visitor.py +58 -0
- exdrf-0.0.1.dev0.dist-info/METADATA +42 -0
- exdrf-0.0.1.dev0.dist-info/RECORD +57 -0
- exdrf-0.0.1.dev0.dist-info/WHEEL +5 -0
- exdrf-0.0.1.dev0.dist-info/top_level.txt +3 -0
- exdrf_tests/__init__.py +0 -0
- exdrf_tests/test_dataset.py +422 -0
- exdrf_tests/test_field.py +109 -0
- exdrf_tests/test_filter.py +425 -0
- exdrf_tests/test_filter_dsl.py +556 -0
- exdrf_tests/test_label_dsl.py +234 -0
- exdrf_tests/test_resource.py +107 -0
- exdrf_tests/test_utils.py +43 -0
- exdrf_tests/test_visitor.py +31 -0
- 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,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})"
|