nuql 0.0.1__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.
- nuql/__init__.py +3 -0
- nuql/api/__init__.py +13 -0
- nuql/api/adapter.py +34 -0
- nuql/api/batch_get/__init__.py +2 -0
- nuql/api/batch_get/batch_get.py +40 -0
- nuql/api/batch_get/queue.py +120 -0
- nuql/api/batch_write.py +99 -0
- nuql/api/condition_check.py +39 -0
- nuql/api/create.py +25 -0
- nuql/api/delete.py +88 -0
- nuql/api/get.py +30 -0
- nuql/api/put_item.py +112 -0
- nuql/api/put_update.py +25 -0
- nuql/api/query/__init__.py +4 -0
- nuql/api/query/condition.py +157 -0
- nuql/api/query/condition_builder.py +211 -0
- nuql/api/query/key_condition.py +200 -0
- nuql/api/query/query.py +166 -0
- nuql/api/transaction.py +145 -0
- nuql/api/update/__init__.py +3 -0
- nuql/api/update/expression_builder.py +33 -0
- nuql/api/update/update_item.py +139 -0
- nuql/api/update/utils.py +126 -0
- nuql/api/upsert.py +32 -0
- nuql/client.py +88 -0
- nuql/connection.py +43 -0
- nuql/exceptions.py +66 -0
- nuql/fields/__init__.py +11 -0
- nuql/fields/boolean.py +29 -0
- nuql/fields/datetime.py +49 -0
- nuql/fields/datetime_timestamp.py +45 -0
- nuql/fields/float.py +40 -0
- nuql/fields/integer.py +40 -0
- nuql/fields/key.py +207 -0
- nuql/fields/list.py +90 -0
- nuql/fields/map.py +67 -0
- nuql/fields/string.py +184 -0
- nuql/fields/ulid.py +39 -0
- nuql/fields/uuid.py +42 -0
- nuql/generators/__init__.py +3 -0
- nuql/generators/datetime.py +37 -0
- nuql/generators/ulid.py +10 -0
- nuql/generators/uuid.py +19 -0
- nuql/resources/__init__.py +4 -0
- nuql/resources/fields/__init__.py +3 -0
- nuql/resources/fields/field.py +153 -0
- nuql/resources/fields/field_map.py +85 -0
- nuql/resources/fields/value.py +5 -0
- nuql/resources/records/__init__.py +3 -0
- nuql/resources/records/projections.py +49 -0
- nuql/resources/records/serialiser.py +144 -0
- nuql/resources/records/validator.py +48 -0
- nuql/resources/tables/__init__.py +2 -0
- nuql/resources/tables/indexes.py +140 -0
- nuql/resources/tables/table.py +151 -0
- nuql/resources/utils/__init__.py +2 -0
- nuql/resources/utils/dict.py +21 -0
- nuql/resources/utils/validators.py +165 -0
- nuql/types/__init__.py +3 -0
- nuql/types/config.py +27 -0
- nuql/types/fields.py +27 -0
- nuql/types/serialisation.py +10 -0
- nuql-0.0.1.dist-info/METADATA +12 -0
- nuql-0.0.1.dist-info/RECORD +65 -0
- nuql-0.0.1.dist-info/WHEEL +4 -0
nuql/fields/list.py
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
__all__ = ['List']
|
2
|
+
|
3
|
+
from typing import List as _List, Any
|
4
|
+
|
5
|
+
import nuql
|
6
|
+
from nuql import resources, types
|
7
|
+
from nuql.resources import FieldBase
|
8
|
+
|
9
|
+
|
10
|
+
class List(FieldBase):
|
11
|
+
type = 'list'
|
12
|
+
of: FieldBase
|
13
|
+
|
14
|
+
def on_init(self) -> None:
|
15
|
+
"""Defines the contents of the list."""
|
16
|
+
if 'of' not in self.config:
|
17
|
+
raise nuql.NuqlError(
|
18
|
+
code='SchemaError',
|
19
|
+
message='Config key \'of\' must be defined for the list field type'
|
20
|
+
)
|
21
|
+
|
22
|
+
# Initialise the configured 'of' field type
|
23
|
+
field_map = resources.create_field_map(
|
24
|
+
fields={'of': self.config['of']},
|
25
|
+
parent=self.parent,
|
26
|
+
field_types=self.parent.provider.fields
|
27
|
+
)
|
28
|
+
self.of = field_map['of']
|
29
|
+
|
30
|
+
def __call__(self, value: Any, action: 'types.SerialisationType', validator: 'resources.Validator') -> Any:
|
31
|
+
"""
|
32
|
+
Encapsulates the internal serialisation logic to prepare for
|
33
|
+
sending the record to DynamoDB.
|
34
|
+
|
35
|
+
:arg value: Deserialised value.
|
36
|
+
:arg action: SerialisationType (`create`, `update`, `write` or `query`).
|
37
|
+
:arg validator: Validator instance.
|
38
|
+
:return: Serialised value.
|
39
|
+
"""
|
40
|
+
has_value = not isinstance(value, resources.EmptyValue)
|
41
|
+
|
42
|
+
# Apply generators if applicable to the field to overwrite the value
|
43
|
+
if action in ['create', 'update', 'write']:
|
44
|
+
if action == 'create' and self.on_create:
|
45
|
+
value = self.on_create()
|
46
|
+
|
47
|
+
if action == 'update' and self.on_update:
|
48
|
+
value = self.on_update()
|
49
|
+
|
50
|
+
if self.on_write:
|
51
|
+
value = self.on_write()
|
52
|
+
|
53
|
+
# Set default value if applicable
|
54
|
+
if not has_value and not value:
|
55
|
+
value = self.default
|
56
|
+
|
57
|
+
# Serialise the value
|
58
|
+
if not isinstance(value, list):
|
59
|
+
value = None
|
60
|
+
else:
|
61
|
+
value = [self.of(item, action, validator) for item in value]
|
62
|
+
|
63
|
+
# Validate required field
|
64
|
+
if self.required and action == 'create' and value is None:
|
65
|
+
validator.add(name=self.name, message='Field is required')
|
66
|
+
|
67
|
+
# Validate against enum
|
68
|
+
if self.enum and has_value and action in ['create', 'update', 'write'] and value not in self.enum:
|
69
|
+
validator.add(name=self.name, message=f'Value must be one of: {", ".join(self.enum)}')
|
70
|
+
|
71
|
+
# Run internal validation
|
72
|
+
self.internal_validation(value, action, validator)
|
73
|
+
|
74
|
+
# Run custom validation logic
|
75
|
+
if self.validator and action in ['create', 'update', 'write']:
|
76
|
+
self.validator(value, validator)
|
77
|
+
|
78
|
+
return value
|
79
|
+
|
80
|
+
def deserialise(self, value: _List[Any] | None) -> _List[Any] | None:
|
81
|
+
"""
|
82
|
+
Deserialises a list of values.
|
83
|
+
|
84
|
+
:arg value: List or None.
|
85
|
+
:return: List or None.
|
86
|
+
"""
|
87
|
+
if not isinstance(value, list):
|
88
|
+
return None
|
89
|
+
|
90
|
+
return [self.of.deserialise(item) for item in value]
|
nuql/fields/map.py
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
__all__ = ['Map']
|
2
|
+
|
3
|
+
from typing import Dict as _Dict, Any
|
4
|
+
|
5
|
+
import nuql
|
6
|
+
from nuql import resources, types
|
7
|
+
|
8
|
+
|
9
|
+
class Map(resources.FieldBase):
|
10
|
+
type = 'map'
|
11
|
+
fields: _Dict[str, Any] = {}
|
12
|
+
serialiser: 'resources.Serialiser' = None
|
13
|
+
|
14
|
+
def on_init(self) -> None:
|
15
|
+
"""Initialises the dict schema."""
|
16
|
+
if 'fields' not in self.config:
|
17
|
+
raise nuql.NuqlError(
|
18
|
+
code='SchemaError',
|
19
|
+
message='Config key \'fields\' must be defined for the dict field type'
|
20
|
+
)
|
21
|
+
|
22
|
+
self.fields = resources.create_field_map(self.config['fields'], self.parent, self.parent.provider.fields)
|
23
|
+
self.serialiser = resources.Serialiser(self)
|
24
|
+
|
25
|
+
def __call__(self, value: Any, action: 'types.SerialisationType', validator: 'resources.Validator') -> Any:
|
26
|
+
"""
|
27
|
+
Encapsulates the internal serialisation logic to prepare for
|
28
|
+
sending the record to DynamoDB.
|
29
|
+
|
30
|
+
:arg value: Deserialised value.
|
31
|
+
:arg action: SerialisationType (`create`, `update`, `write` or `query`).
|
32
|
+
:arg validator: Validator instance.
|
33
|
+
:return: Serialised value.
|
34
|
+
"""
|
35
|
+
has_value = not isinstance(value, resources.EmptyValue)
|
36
|
+
|
37
|
+
# Apply generators if applicable to the field to overwrite the value
|
38
|
+
if action in ['create', 'update', 'write']:
|
39
|
+
if action == 'create' and self.on_create:
|
40
|
+
value = self.on_create()
|
41
|
+
|
42
|
+
if action == 'update' and self.on_update:
|
43
|
+
value = self.on_update()
|
44
|
+
|
45
|
+
if self.on_write:
|
46
|
+
value = self.on_write()
|
47
|
+
|
48
|
+
# Set default value if applicable
|
49
|
+
if not has_value and not value:
|
50
|
+
value = self.default
|
51
|
+
|
52
|
+
# Serialise the value
|
53
|
+
if value:
|
54
|
+
value = self.serialiser.serialise(action, value, validator)
|
55
|
+
|
56
|
+
# Validate required field
|
57
|
+
if self.required and action == 'create' and value is None:
|
58
|
+
validator.add(name=self.name, message='Field is required')
|
59
|
+
|
60
|
+
# Run internal validation
|
61
|
+
self.internal_validation(value, action, validator)
|
62
|
+
|
63
|
+
# Run custom validation logic
|
64
|
+
if self.validator and action in ['create', 'update', 'write']:
|
65
|
+
self.validator(value, validator)
|
66
|
+
|
67
|
+
return value
|
nuql/fields/string.py
ADDED
@@ -0,0 +1,184 @@
|
|
1
|
+
__all__ = ['String']
|
2
|
+
|
3
|
+
import re
|
4
|
+
from string import Template
|
5
|
+
from typing import List, Dict, Any
|
6
|
+
|
7
|
+
import nuql
|
8
|
+
from nuql import resources, types
|
9
|
+
|
10
|
+
|
11
|
+
TEMPLATE_PATTERN = r'\$\{(\w+)}'
|
12
|
+
|
13
|
+
|
14
|
+
class String(resources.FieldBase):
|
15
|
+
type = 'string'
|
16
|
+
is_template = False
|
17
|
+
|
18
|
+
def __call__(self, value: Any, action: 'types.SerialisationType', validator: 'resources.Validator') -> Any:
|
19
|
+
"""
|
20
|
+
Encapsulates the internal serialisation logic to prepare for
|
21
|
+
sending the record to DynamoDB.
|
22
|
+
|
23
|
+
:arg value: Deserialised value.
|
24
|
+
:arg action: SerialisationType (`create`, `update`, `write` or `query`).
|
25
|
+
:arg validator: Validator instance.
|
26
|
+
:return: Serialised value.
|
27
|
+
"""
|
28
|
+
has_value = not isinstance(value, resources.EmptyValue)
|
29
|
+
|
30
|
+
# Apply generators if applicable to the field to overwrite the value
|
31
|
+
if action in ['create', 'update', 'write']:
|
32
|
+
if action == 'create' and self.on_create:
|
33
|
+
value = self.on_create()
|
34
|
+
|
35
|
+
if action == 'update' and self.on_update:
|
36
|
+
value = self.on_update()
|
37
|
+
|
38
|
+
if self.on_write:
|
39
|
+
value = self.on_write()
|
40
|
+
|
41
|
+
# Set default value if applicable
|
42
|
+
if not has_value and not value and not self.value and not self.is_template:
|
43
|
+
value = self.default
|
44
|
+
|
45
|
+
if self.value and not self.is_template:
|
46
|
+
value = self.value
|
47
|
+
|
48
|
+
# Serialise the value
|
49
|
+
if self.is_template:
|
50
|
+
value = self.serialise_template(value, action, validator)
|
51
|
+
else:
|
52
|
+
value = self.serialise(value)
|
53
|
+
|
54
|
+
# Validate required field
|
55
|
+
if self.required and action == 'create' and value is None:
|
56
|
+
validator.add(name=self.name, message='Field is required')
|
57
|
+
|
58
|
+
# Run internal validation
|
59
|
+
self.internal_validation(value, action, validator)
|
60
|
+
|
61
|
+
# Run custom validation logic
|
62
|
+
if self.validator and action in ['create', 'update', 'write']:
|
63
|
+
self.validator(value, validator)
|
64
|
+
|
65
|
+
return value
|
66
|
+
|
67
|
+
def on_init(self) -> None:
|
68
|
+
"""Initialises the string field when a template is defined."""
|
69
|
+
self.is_template = self.value is not None and bool(re.search(TEMPLATE_PATTERN, self.value))
|
70
|
+
|
71
|
+
def callback(field_map: dict) -> None:
|
72
|
+
"""Callback fn to configure projected fields on the schema."""
|
73
|
+
for key in self.find_projections(self.value):
|
74
|
+
if key not in field_map:
|
75
|
+
raise nuql.NuqlError(
|
76
|
+
code='TemplateStringError',
|
77
|
+
message=f'Field \'{key}\' (projected on string field '
|
78
|
+
f'\'{self.name}\') is not defined in the schema'
|
79
|
+
)
|
80
|
+
|
81
|
+
# Add reference to this field on the projected field
|
82
|
+
field_map[key].projected_from.append(self.name)
|
83
|
+
self.projects_fields.append(key)
|
84
|
+
|
85
|
+
if self.init_callback is not None and self.is_template:
|
86
|
+
self.init_callback(callback)
|
87
|
+
|
88
|
+
def serialise(self, value: str | None) -> str | None:
|
89
|
+
"""
|
90
|
+
Serialises a string value.
|
91
|
+
|
92
|
+
:arg value: Value.
|
93
|
+
:return: Serialised value
|
94
|
+
"""
|
95
|
+
return str(value) if value else None
|
96
|
+
|
97
|
+
def deserialise(self, value: str | None) -> str | None:
|
98
|
+
"""
|
99
|
+
Deserialises a string value.
|
100
|
+
|
101
|
+
:arg value: String value.
|
102
|
+
:return: String value.
|
103
|
+
"""
|
104
|
+
return str(value) if value else None
|
105
|
+
|
106
|
+
def serialise_template(
|
107
|
+
self,
|
108
|
+
value: Dict[str, Any],
|
109
|
+
action: 'types.SerialisationType',
|
110
|
+
validator: 'resources.Validator'
|
111
|
+
) -> str | None:
|
112
|
+
"""
|
113
|
+
Serialises a template string.
|
114
|
+
|
115
|
+
:arg value: Dict of projections.
|
116
|
+
:arg action: Serialisation type.
|
117
|
+
:arg validator: Validator instance.
|
118
|
+
:return: String value.
|
119
|
+
"""
|
120
|
+
if not isinstance(value, dict):
|
121
|
+
value = {}
|
122
|
+
|
123
|
+
# Add not provided keys as empty strings
|
124
|
+
for key in self.find_projections(self.value):
|
125
|
+
if key not in value:
|
126
|
+
value[key] = None
|
127
|
+
|
128
|
+
serialised = {}
|
129
|
+
|
130
|
+
# Serialise values before substituting
|
131
|
+
for key, deserialised_value in value.items():
|
132
|
+
field = self.parent.fields.get(key)
|
133
|
+
|
134
|
+
if not field:
|
135
|
+
raise nuql.NuqlError(
|
136
|
+
code='TemplateStringError',
|
137
|
+
message=f'Field \'{key}\' (projected on string field '
|
138
|
+
f'\'{self.name}\') is not defined in the schema'
|
139
|
+
)
|
140
|
+
|
141
|
+
serialised_value = field(deserialised_value, action, validator)
|
142
|
+
serialised[key] = serialised_value if serialised_value else ''
|
143
|
+
|
144
|
+
template = Template(self.value)
|
145
|
+
return template.substitute(serialised)
|
146
|
+
|
147
|
+
def deserialise_template(self, value: str | None) -> Dict[str, Any]:
|
148
|
+
"""
|
149
|
+
Deserialises a string template.
|
150
|
+
|
151
|
+
:arg value: String value or None.
|
152
|
+
:return: Dict of projections.
|
153
|
+
"""
|
154
|
+
if not value:
|
155
|
+
return {}
|
156
|
+
|
157
|
+
pattern = re.sub(TEMPLATE_PATTERN, r'(?P<\1>[^&#]+)', self.value)
|
158
|
+
match = re.fullmatch(pattern, value)
|
159
|
+
output = {}
|
160
|
+
|
161
|
+
for key, serialised_value in (match.groupdict() if match else {}).items():
|
162
|
+
field = self.parent.fields.get(key)
|
163
|
+
|
164
|
+
if not field:
|
165
|
+
raise nuql.NuqlError(
|
166
|
+
code='TemplateStringError',
|
167
|
+
message=f'Field \'{key}\' (projected on string field '
|
168
|
+
f'\'{self.name}\') is not defined in the schema'
|
169
|
+
)
|
170
|
+
|
171
|
+
deserialised_value = field.deserialise(serialised_value)
|
172
|
+
output[key] = deserialised_value
|
173
|
+
|
174
|
+
return output
|
175
|
+
|
176
|
+
@staticmethod
|
177
|
+
def find_projections(value: str) -> List[str]:
|
178
|
+
"""
|
179
|
+
Finds projections in the value provided as templates '${field_name}'.
|
180
|
+
|
181
|
+
:arg value: Value to parse.
|
182
|
+
:return: List of field names.
|
183
|
+
"""
|
184
|
+
return re.findall(TEMPLATE_PATTERN, value)
|
nuql/fields/ulid.py
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
__all__ = ['Ulid']
|
2
|
+
|
3
|
+
from ulid import ULID
|
4
|
+
|
5
|
+
from nuql import resources
|
6
|
+
|
7
|
+
|
8
|
+
class Ulid(resources.FieldBase):
|
9
|
+
type = 'ulid'
|
10
|
+
|
11
|
+
def serialise(self, value: ULID | str | None) -> str | None:
|
12
|
+
"""
|
13
|
+
Serialises a ULID value.
|
14
|
+
|
15
|
+
:arg value: ULID, str or None.
|
16
|
+
:return: str or None.
|
17
|
+
"""
|
18
|
+
if isinstance(value, ULID):
|
19
|
+
return str(value)
|
20
|
+
if isinstance(value, str):
|
21
|
+
try:
|
22
|
+
return str(ULID.from_str(value))
|
23
|
+
except ValueError:
|
24
|
+
return None
|
25
|
+
return None
|
26
|
+
|
27
|
+
def deserialise(self, value: str | None) -> str | None:
|
28
|
+
"""
|
29
|
+
Deserialises a ULID value.
|
30
|
+
|
31
|
+
:arg value: str or None.
|
32
|
+
:return: str or None.
|
33
|
+
"""
|
34
|
+
if isinstance(value, str):
|
35
|
+
# try:
|
36
|
+
return str(ULID.from_str(value))
|
37
|
+
# except ValueError:
|
38
|
+
# return None
|
39
|
+
return None
|
nuql/fields/uuid.py
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
__all__ = ['Uuid']
|
2
|
+
|
3
|
+
from uuid import UUID as NATIVE_UUID
|
4
|
+
from uuid_utils import UUID
|
5
|
+
|
6
|
+
from nuql import resources
|
7
|
+
|
8
|
+
|
9
|
+
class Uuid(resources.FieldBase):
|
10
|
+
type = 'uuid'
|
11
|
+
|
12
|
+
def serialise(self, value: NATIVE_UUID | UUID | str | None) -> str | None:
|
13
|
+
"""
|
14
|
+
Serialises a UUID value.
|
15
|
+
|
16
|
+
:arg value: UUID, str or None.
|
17
|
+
:return: str or None.
|
18
|
+
"""
|
19
|
+
if isinstance(value, (NATIVE_UUID, UUID)):
|
20
|
+
return str(value)
|
21
|
+
|
22
|
+
if isinstance(value, str):
|
23
|
+
try:
|
24
|
+
return str(UUID(value))
|
25
|
+
except ValueError:
|
26
|
+
return None
|
27
|
+
|
28
|
+
return None
|
29
|
+
|
30
|
+
def deserialise(self, value: str | None) -> str | None:
|
31
|
+
"""
|
32
|
+
Deserialises a UUID value (only to string).
|
33
|
+
|
34
|
+
:arg value: str or None.
|
35
|
+
:return: str or None.
|
36
|
+
"""
|
37
|
+
if isinstance(value, str):
|
38
|
+
try:
|
39
|
+
return str(UUID(value))
|
40
|
+
except ValueError:
|
41
|
+
return None
|
42
|
+
return None
|
@@ -0,0 +1,37 @@
|
|
1
|
+
__all__ = ["Datetime"]
|
2
|
+
|
3
|
+
from datetime import datetime, timedelta, UTC
|
4
|
+
|
5
|
+
|
6
|
+
class Datetime:
|
7
|
+
@classmethod
|
8
|
+
def now(cls):
|
9
|
+
"""Generates current UTC time"""
|
10
|
+
def generator():
|
11
|
+
return datetime.now(UTC)
|
12
|
+
return generator
|
13
|
+
|
14
|
+
@classmethod
|
15
|
+
def relative(
|
16
|
+
cls,
|
17
|
+
days: float = 0,
|
18
|
+
seconds: float = 0,
|
19
|
+
microseconds: float = 0,
|
20
|
+
milliseconds: float = 0,
|
21
|
+
minutes: float = 0,
|
22
|
+
hours: float = 0,
|
23
|
+
weeks: float = 0
|
24
|
+
):
|
25
|
+
"""Generates a UTC datetime relative to now."""
|
26
|
+
def generator():
|
27
|
+
return datetime.now(UTC) + timedelta(
|
28
|
+
days=days,
|
29
|
+
seconds=seconds,
|
30
|
+
microseconds=microseconds,
|
31
|
+
milliseconds=milliseconds,
|
32
|
+
minutes=minutes,
|
33
|
+
hours=hours,
|
34
|
+
weeks=weeks
|
35
|
+
)
|
36
|
+
|
37
|
+
return generator
|
nuql/generators/ulid.py
ADDED
nuql/generators/uuid.py
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
__all__ = ['Uuid']
|
2
|
+
|
3
|
+
from uuid_utils import uuid4, uuid7
|
4
|
+
|
5
|
+
|
6
|
+
class Uuid:
|
7
|
+
@classmethod
|
8
|
+
def v4(cls):
|
9
|
+
"""Generates a random UUID v4"""
|
10
|
+
def generator():
|
11
|
+
return uuid4()
|
12
|
+
return generator
|
13
|
+
|
14
|
+
@classmethod
|
15
|
+
def v7(cls):
|
16
|
+
"""Generates a random UUID v7"""
|
17
|
+
def generator():
|
18
|
+
return uuid7()
|
19
|
+
return generator
|
@@ -0,0 +1,153 @@
|
|
1
|
+
__all__ = ['FieldBase']
|
2
|
+
|
3
|
+
from typing import Any, List, Optional, Callable
|
4
|
+
|
5
|
+
import nuql
|
6
|
+
from nuql import resources, types
|
7
|
+
|
8
|
+
|
9
|
+
class FieldBase:
|
10
|
+
type: str = None
|
11
|
+
|
12
|
+
def __init__(
|
13
|
+
self,
|
14
|
+
name: str,
|
15
|
+
config: 'types.FieldConfig',
|
16
|
+
parent: 'resources.Table',
|
17
|
+
init_callback: Callable[[Callable], None] | None = None
|
18
|
+
) -> None:
|
19
|
+
"""
|
20
|
+
Wrapper for the handling of field serialisation and deserialisation.
|
21
|
+
|
22
|
+
:arg name: Field name.
|
23
|
+
:arg config: Field config dict.
|
24
|
+
:arg parent: Parent instance.
|
25
|
+
:param init_callback: Optional init callback.
|
26
|
+
"""
|
27
|
+
self.name = name
|
28
|
+
self.config = config
|
29
|
+
self.parent = parent
|
30
|
+
self.init_callback = init_callback
|
31
|
+
|
32
|
+
# Handle 'KEY' field type
|
33
|
+
self.projected_from = []
|
34
|
+
self.projects_fields = []
|
35
|
+
|
36
|
+
self.on_init()
|
37
|
+
|
38
|
+
@property
|
39
|
+
def required(self) -> bool:
|
40
|
+
return self.config.get('required', False)
|
41
|
+
|
42
|
+
@property
|
43
|
+
def default(self) -> Any:
|
44
|
+
return self.config.get('default', None)
|
45
|
+
|
46
|
+
@property
|
47
|
+
def value(self) -> Any:
|
48
|
+
return self.config.get('value', None)
|
49
|
+
|
50
|
+
@property
|
51
|
+
def on_create(self) -> Optional['types.GeneratorCallback']:
|
52
|
+
return self.config.get('on_create', None)
|
53
|
+
|
54
|
+
@property
|
55
|
+
def on_update(self) -> Optional['types.GeneratorCallback']:
|
56
|
+
return self.config.get('on_update', None)
|
57
|
+
|
58
|
+
@property
|
59
|
+
def on_write(self) -> Optional['types.GeneratorCallback']:
|
60
|
+
return self.config.get('on_write', None)
|
61
|
+
|
62
|
+
@property
|
63
|
+
def validator(self) -> Optional['types.ValidatorCallback']:
|
64
|
+
return self.config.get('validator', None)
|
65
|
+
|
66
|
+
@property
|
67
|
+
def enum(self) -> List[Any] | None:
|
68
|
+
return self.config.get('enum', None)
|
69
|
+
|
70
|
+
def __call__(self, value: Any, action: 'types.SerialisationType', validator: 'resources.Validator') -> Any:
|
71
|
+
"""
|
72
|
+
Encapsulates the internal serialisation logic to prepare for
|
73
|
+
sending the record to DynamoDB.
|
74
|
+
|
75
|
+
:arg value: Deserialised value.
|
76
|
+
:arg action: SerialisationType (`create`, `update`, `write` or `query`).
|
77
|
+
:arg validator: Validator instance.
|
78
|
+
:return: Serialised value.
|
79
|
+
"""
|
80
|
+
has_value = not isinstance(value, resources.EmptyValue)
|
81
|
+
|
82
|
+
# Apply generators if applicable to the field to overwrite the value
|
83
|
+
if action in ['create', 'update', 'write']:
|
84
|
+
if action == 'create' and self.on_create:
|
85
|
+
value = self.on_create()
|
86
|
+
|
87
|
+
if action == 'update' and self.on_update:
|
88
|
+
value = self.on_update()
|
89
|
+
|
90
|
+
if self.on_write:
|
91
|
+
value = self.on_write()
|
92
|
+
|
93
|
+
# Set default value if applicable
|
94
|
+
if not has_value and not value:
|
95
|
+
value = self.default
|
96
|
+
|
97
|
+
# Serialise the value
|
98
|
+
value = self.serialise(value)
|
99
|
+
|
100
|
+
# Validate required field
|
101
|
+
if self.required and action == 'create' and value is None:
|
102
|
+
validator.add(name=self.name, message='Field is required')
|
103
|
+
|
104
|
+
# Validate against enum
|
105
|
+
if self.enum and has_value and action in ['create', 'update', 'write'] and value not in self.enum:
|
106
|
+
validator.add(name=self.name, message=f'Value must be one of: {", ".join(self.enum)}')
|
107
|
+
|
108
|
+
# Run internal validation
|
109
|
+
self.internal_validation(value, action, validator)
|
110
|
+
|
111
|
+
# Run custom validation logic
|
112
|
+
if self.validator and action in ['create', 'update', 'write']:
|
113
|
+
self.validator(value, validator)
|
114
|
+
|
115
|
+
return value
|
116
|
+
|
117
|
+
def serialise(self, value: Any) -> Any:
|
118
|
+
"""
|
119
|
+
Serialise/marshal the field value into DynamoDB format.
|
120
|
+
|
121
|
+
:arg value: Deserialised value.
|
122
|
+
:return: Serialised value.
|
123
|
+
"""
|
124
|
+
raise nuql.NuqlError(
|
125
|
+
code='NotImplementedError',
|
126
|
+
message='Serialisation has not been implemented for this field type.'
|
127
|
+
)
|
128
|
+
|
129
|
+
def deserialise(self, value: Any) -> Any:
|
130
|
+
"""
|
131
|
+
Deserialise/unmarshal the field value from DynamoDB format.
|
132
|
+
|
133
|
+
:arg value: Serialised value.
|
134
|
+
:return: Deserialised value.
|
135
|
+
"""
|
136
|
+
raise nuql.NuqlError(
|
137
|
+
code='NotImplementedError',
|
138
|
+
message='Deserialisation has not been implemented for this field type.'
|
139
|
+
)
|
140
|
+
|
141
|
+
def on_init(self) -> None:
|
142
|
+
"""Custom initialisation logic for the field."""
|
143
|
+
pass
|
144
|
+
|
145
|
+
def internal_validation(self, value: Any, action: 'types.SerialisationType', validator: 'resources.Validator'):
|
146
|
+
"""
|
147
|
+
Perform internal validation on the field.
|
148
|
+
|
149
|
+
:arg value: Value.
|
150
|
+
:arg action: Serialisation action.
|
151
|
+
:arg validator: Validator instance.
|
152
|
+
"""
|
153
|
+
pass
|