clear-skies 2.0.3__py3-none-any.whl → 2.0.5__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.

Potentially problematic release.


This version of clear-skies might be problematic. Click here for more details.

Files changed (251) hide show
  1. clear_skies-2.0.5.dist-info/METADATA +74 -0
  2. clear_skies-2.0.5.dist-info/RECORD +4 -0
  3. {clear_skies-2.0.3.dist-info → clear_skies-2.0.5.dist-info}/WHEEL +1 -1
  4. clear_skies-2.0.3.dist-info/METADATA +0 -46
  5. clear_skies-2.0.3.dist-info/RECORD +0 -249
  6. clearskies/__init__.py +0 -59
  7. clearskies/action.py +0 -7
  8. clearskies/authentication/__init__.py +0 -15
  9. clearskies/authentication/authentication.py +0 -46
  10. clearskies/authentication/authorization.py +0 -16
  11. clearskies/authentication/authorization_pass_through.py +0 -20
  12. clearskies/authentication/jwks.py +0 -163
  13. clearskies/authentication/public.py +0 -5
  14. clearskies/authentication/secret_bearer.py +0 -553
  15. clearskies/autodoc/__init__.py +0 -8
  16. clearskies/autodoc/formats/__init__.py +0 -5
  17. clearskies/autodoc/formats/oai3_json/__init__.py +0 -7
  18. clearskies/autodoc/formats/oai3_json/oai3_json.py +0 -87
  19. clearskies/autodoc/formats/oai3_json/oai3_schema_resolver.py +0 -15
  20. clearskies/autodoc/formats/oai3_json/parameter.py +0 -35
  21. clearskies/autodoc/formats/oai3_json/request.py +0 -68
  22. clearskies/autodoc/formats/oai3_json/response.py +0 -28
  23. clearskies/autodoc/formats/oai3_json/schema/__init__.py +0 -11
  24. clearskies/autodoc/formats/oai3_json/schema/array.py +0 -9
  25. clearskies/autodoc/formats/oai3_json/schema/default.py +0 -13
  26. clearskies/autodoc/formats/oai3_json/schema/enum.py +0 -7
  27. clearskies/autodoc/formats/oai3_json/schema/object.py +0 -29
  28. clearskies/autodoc/formats/oai3_json/test.json +0 -1985
  29. clearskies/autodoc/py.typed +0 -0
  30. clearskies/autodoc/request/__init__.py +0 -15
  31. clearskies/autodoc/request/header.py +0 -6
  32. clearskies/autodoc/request/json_body.py +0 -6
  33. clearskies/autodoc/request/parameter.py +0 -8
  34. clearskies/autodoc/request/request.py +0 -38
  35. clearskies/autodoc/request/url_parameter.py +0 -6
  36. clearskies/autodoc/request/url_path.py +0 -6
  37. clearskies/autodoc/response/__init__.py +0 -5
  38. clearskies/autodoc/response/response.py +0 -9
  39. clearskies/autodoc/schema/__init__.py +0 -31
  40. clearskies/autodoc/schema/array.py +0 -10
  41. clearskies/autodoc/schema/base64.py +0 -8
  42. clearskies/autodoc/schema/boolean.py +0 -5
  43. clearskies/autodoc/schema/date.py +0 -5
  44. clearskies/autodoc/schema/datetime.py +0 -5
  45. clearskies/autodoc/schema/double.py +0 -5
  46. clearskies/autodoc/schema/enum.py +0 -17
  47. clearskies/autodoc/schema/integer.py +0 -6
  48. clearskies/autodoc/schema/long.py +0 -5
  49. clearskies/autodoc/schema/number.py +0 -6
  50. clearskies/autodoc/schema/object.py +0 -13
  51. clearskies/autodoc/schema/password.py +0 -5
  52. clearskies/autodoc/schema/schema.py +0 -11
  53. clearskies/autodoc/schema/string.py +0 -5
  54. clearskies/backends/__init__.py +0 -65
  55. clearskies/backends/api_backend.py +0 -1178
  56. clearskies/backends/backend.py +0 -136
  57. clearskies/backends/cursor_backend.py +0 -335
  58. clearskies/backends/memory_backend.py +0 -797
  59. clearskies/backends/secrets_backend.py +0 -106
  60. clearskies/column.py +0 -1233
  61. clearskies/columns/__init__.py +0 -71
  62. clearskies/columns/audit.py +0 -206
  63. clearskies/columns/belongs_to_id.py +0 -483
  64. clearskies/columns/belongs_to_model.py +0 -132
  65. clearskies/columns/belongs_to_self.py +0 -105
  66. clearskies/columns/boolean.py +0 -113
  67. clearskies/columns/category_tree.py +0 -275
  68. clearskies/columns/category_tree_ancestors.py +0 -51
  69. clearskies/columns/category_tree_children.py +0 -127
  70. clearskies/columns/category_tree_descendants.py +0 -48
  71. clearskies/columns/created.py +0 -95
  72. clearskies/columns/created_by_authorization_data.py +0 -116
  73. clearskies/columns/created_by_header.py +0 -99
  74. clearskies/columns/created_by_ip.py +0 -92
  75. clearskies/columns/created_by_routing_data.py +0 -97
  76. clearskies/columns/created_by_user_agent.py +0 -92
  77. clearskies/columns/date.py +0 -234
  78. clearskies/columns/datetime.py +0 -282
  79. clearskies/columns/email.py +0 -76
  80. clearskies/columns/float.py +0 -153
  81. clearskies/columns/has_many.py +0 -505
  82. clearskies/columns/has_many_self.py +0 -56
  83. clearskies/columns/has_one.py +0 -14
  84. clearskies/columns/integer.py +0 -160
  85. clearskies/columns/json.py +0 -126
  86. clearskies/columns/many_to_many_ids.py +0 -337
  87. clearskies/columns/many_to_many_ids_with_data.py +0 -274
  88. clearskies/columns/many_to_many_models.py +0 -158
  89. clearskies/columns/many_to_many_pivots.py +0 -134
  90. clearskies/columns/phone.py +0 -159
  91. clearskies/columns/select.py +0 -92
  92. clearskies/columns/string.py +0 -102
  93. clearskies/columns/timestamp.py +0 -164
  94. clearskies/columns/updated.py +0 -110
  95. clearskies/columns/uuid.py +0 -86
  96. clearskies/configs/README.md +0 -105
  97. clearskies/configs/__init__.py +0 -162
  98. clearskies/configs/actions.py +0 -43
  99. clearskies/configs/any.py +0 -13
  100. clearskies/configs/any_dict.py +0 -22
  101. clearskies/configs/any_dict_or_callable.py +0 -23
  102. clearskies/configs/authentication.py +0 -23
  103. clearskies/configs/authorization.py +0 -23
  104. clearskies/configs/boolean.py +0 -16
  105. clearskies/configs/boolean_or_callable.py +0 -18
  106. clearskies/configs/callable_config.py +0 -18
  107. clearskies/configs/columns.py +0 -34
  108. clearskies/configs/conditions.py +0 -30
  109. clearskies/configs/config.py +0 -24
  110. clearskies/configs/datetime.py +0 -18
  111. clearskies/configs/datetime_or_callable.py +0 -19
  112. clearskies/configs/endpoint.py +0 -23
  113. clearskies/configs/endpoint_list.py +0 -28
  114. clearskies/configs/float.py +0 -16
  115. clearskies/configs/float_or_callable.py +0 -18
  116. clearskies/configs/integer.py +0 -16
  117. clearskies/configs/integer_or_callable.py +0 -18
  118. clearskies/configs/joins.py +0 -30
  119. clearskies/configs/list_any_dict.py +0 -30
  120. clearskies/configs/list_any_dict_or_callable.py +0 -31
  121. clearskies/configs/model_class.py +0 -35
  122. clearskies/configs/model_column.py +0 -65
  123. clearskies/configs/model_columns.py +0 -56
  124. clearskies/configs/model_destination_name.py +0 -25
  125. clearskies/configs/model_to_id_column.py +0 -43
  126. clearskies/configs/readable_model_column.py +0 -9
  127. clearskies/configs/readable_model_columns.py +0 -9
  128. clearskies/configs/schema.py +0 -23
  129. clearskies/configs/searchable_model_columns.py +0 -9
  130. clearskies/configs/security_headers.py +0 -39
  131. clearskies/configs/select.py +0 -26
  132. clearskies/configs/select_list.py +0 -47
  133. clearskies/configs/string.py +0 -29
  134. clearskies/configs/string_dict.py +0 -32
  135. clearskies/configs/string_list.py +0 -32
  136. clearskies/configs/string_list_or_callable.py +0 -35
  137. clearskies/configs/string_or_callable.py +0 -18
  138. clearskies/configs/timedelta.py +0 -18
  139. clearskies/configs/timezone.py +0 -18
  140. clearskies/configs/url.py +0 -23
  141. clearskies/configs/validators.py +0 -45
  142. clearskies/configs/writeable_model_column.py +0 -9
  143. clearskies/configs/writeable_model_columns.py +0 -9
  144. clearskies/configurable.py +0 -76
  145. clearskies/contexts/__init__.py +0 -11
  146. clearskies/contexts/cli.py +0 -117
  147. clearskies/contexts/context.py +0 -98
  148. clearskies/contexts/wsgi.py +0 -76
  149. clearskies/contexts/wsgi_ref.py +0 -82
  150. clearskies/decorators.py +0 -33
  151. clearskies/di/__init__.py +0 -14
  152. clearskies/di/additional_config.py +0 -130
  153. clearskies/di/additional_config_auto_import.py +0 -17
  154. clearskies/di/di.py +0 -968
  155. clearskies/di/inject/__init__.py +0 -23
  156. clearskies/di/inject/by_class.py +0 -21
  157. clearskies/di/inject/by_name.py +0 -18
  158. clearskies/di/inject/di.py +0 -13
  159. clearskies/di/inject/environment.py +0 -14
  160. clearskies/di/inject/input_output.py +0 -20
  161. clearskies/di/inject/now.py +0 -13
  162. clearskies/di/inject/requests.py +0 -13
  163. clearskies/di/inject/secrets.py +0 -14
  164. clearskies/di/inject/utcnow.py +0 -13
  165. clearskies/di/inject/uuid.py +0 -15
  166. clearskies/di/injectable.py +0 -29
  167. clearskies/di/injectable_properties.py +0 -131
  168. clearskies/di/test_module/__init__.py +0 -6
  169. clearskies/di/test_module/another_module/__init__.py +0 -2
  170. clearskies/di/test_module/module_class.py +0 -5
  171. clearskies/end.py +0 -183
  172. clearskies/endpoint.py +0 -1310
  173. clearskies/endpoint_group.py +0 -310
  174. clearskies/endpoints/__init__.py +0 -23
  175. clearskies/endpoints/advanced_search.py +0 -526
  176. clearskies/endpoints/callable.py +0 -388
  177. clearskies/endpoints/create.py +0 -202
  178. clearskies/endpoints/delete.py +0 -139
  179. clearskies/endpoints/get.py +0 -275
  180. clearskies/endpoints/health_check.py +0 -181
  181. clearskies/endpoints/list.py +0 -573
  182. clearskies/endpoints/restful_api.py +0 -427
  183. clearskies/endpoints/simple_search.py +0 -286
  184. clearskies/endpoints/update.py +0 -190
  185. clearskies/environment.py +0 -104
  186. clearskies/exceptions/__init__.py +0 -17
  187. clearskies/exceptions/authentication.py +0 -2
  188. clearskies/exceptions/authorization.py +0 -2
  189. clearskies/exceptions/client_error.py +0 -2
  190. clearskies/exceptions/input_errors.py +0 -4
  191. clearskies/exceptions/moved_permanently.py +0 -3
  192. clearskies/exceptions/moved_temporarily.py +0 -3
  193. clearskies/exceptions/not_found.py +0 -2
  194. clearskies/functional/__init__.py +0 -7
  195. clearskies/functional/routing.py +0 -92
  196. clearskies/functional/string.py +0 -112
  197. clearskies/functional/validations.py +0 -76
  198. clearskies/input_outputs/__init__.py +0 -13
  199. clearskies/input_outputs/cli.py +0 -171
  200. clearskies/input_outputs/exceptions/__init__.py +0 -2
  201. clearskies/input_outputs/exceptions/cli_input_error.py +0 -2
  202. clearskies/input_outputs/exceptions/cli_not_found.py +0 -2
  203. clearskies/input_outputs/headers.py +0 -45
  204. clearskies/input_outputs/input_output.py +0 -138
  205. clearskies/input_outputs/programmatic.py +0 -69
  206. clearskies/input_outputs/py.typed +0 -0
  207. clearskies/input_outputs/wsgi.py +0 -77
  208. clearskies/model.py +0 -1922
  209. clearskies/py.typed +0 -0
  210. clearskies/query/__init__.py +0 -12
  211. clearskies/query/condition.py +0 -223
  212. clearskies/query/join.py +0 -136
  213. clearskies/query/query.py +0 -196
  214. clearskies/query/sort.py +0 -27
  215. clearskies/schema.py +0 -82
  216. clearskies/secrets/__init__.py +0 -6
  217. clearskies/secrets/additional_configs/__init__.py +0 -32
  218. clearskies/secrets/additional_configs/mysql_connection_dynamic_producer.py +0 -61
  219. clearskies/secrets/additional_configs/mysql_connection_dynamic_producer_via_ssh_cert_bastion.py +0 -160
  220. clearskies/secrets/akeyless.py +0 -182
  221. clearskies/secrets/exceptions/__init__.py +0 -1
  222. clearskies/secrets/exceptions/not_found.py +0 -2
  223. clearskies/secrets/secrets.py +0 -38
  224. clearskies/security_header.py +0 -15
  225. clearskies/security_headers/__init__.py +0 -11
  226. clearskies/security_headers/cache_control.py +0 -67
  227. clearskies/security_headers/cors.py +0 -50
  228. clearskies/security_headers/csp.py +0 -94
  229. clearskies/security_headers/hsts.py +0 -22
  230. clearskies/security_headers/x_content_type_options.py +0 -0
  231. clearskies/security_headers/x_frame_options.py +0 -0
  232. clearskies/test_base.py +0 -8
  233. clearskies/typing.py +0 -11
  234. clearskies/validator.py +0 -37
  235. clearskies/validators/__init__.py +0 -33
  236. clearskies/validators/after_column.py +0 -62
  237. clearskies/validators/before_column.py +0 -13
  238. clearskies/validators/in_the_future.py +0 -32
  239. clearskies/validators/in_the_future_at_least.py +0 -11
  240. clearskies/validators/in_the_future_at_most.py +0 -10
  241. clearskies/validators/in_the_past.py +0 -32
  242. clearskies/validators/in_the_past_at_least.py +0 -10
  243. clearskies/validators/in_the_past_at_most.py +0 -10
  244. clearskies/validators/maximum_length.py +0 -26
  245. clearskies/validators/maximum_value.py +0 -29
  246. clearskies/validators/minimum_length.py +0 -26
  247. clearskies/validators/minimum_value.py +0 -29
  248. clearskies/validators/required.py +0 -34
  249. clearskies/validators/timedelta.py +0 -59
  250. clearskies/validators/unique.py +0 -30
  251. {clear_skies-2.0.3.dist-info → clear_skies-2.0.5.dist-info/licenses}/LICENSE +0 -0
@@ -1,159 +0,0 @@
1
- import re
2
- from typing import Any, Callable
3
-
4
- import clearskies.decorators
5
- import clearskies.typing
6
- from clearskies import configs
7
- from clearskies.columns.string import String
8
-
9
-
10
- class Phone(String):
11
- """
12
- A string column that stores a phone number.
13
-
14
- The main difference between this and a plain string column is that this will validate that the string contains
15
- a phone number (containing only digits, dashes, spaces, plus sign, and parenthesis) of the appropriate length.
16
- When persisting the value to the backend, this column removes all non-digit characters.
17
-
18
- If you also set the usa_only flag to true then it will also validate that it is a valid US number containing
19
- 9 digits and, optionally, a leading `1`. Example:
20
-
21
- ```python
22
- import clearskies
23
-
24
-
25
- class User(clearskies.Model):
26
- id_column_name = "id"
27
- backend = clearskies.backends.MemoryBackend()
28
-
29
- id = clearskies.columns.Uuid()
30
- name = clearskies.columns.String()
31
- phone = clearskies.columns.Phone(usa_only=True)
32
-
33
-
34
- wsgi = clearskies.contexts.WsgiRef(
35
- clearskies.endpoints.Create(
36
- User,
37
- writeable_column_names=["name", "phone"],
38
- readable_column_names=["id", "name", "phone"],
39
- ),
40
- )
41
- wsgi()
42
- ```
43
-
44
- Which you can invoke:
45
-
46
- ```bash
47
- $ curl http://localhost:8080 -d '{"name":"John Doe", "phone": "+1 (555) 451-1234"}' | jq
48
- {
49
- "status": "success",
50
- "error": "",
51
- "data": {
52
- "id": "e2b4bdad-b70f-4d44-a94c-0e265868b4d2",
53
- "name": "John Doe",
54
- "phone": "15554511234"
55
- },
56
- "pagination": {},
57
- "input_errors": {}
58
- }
59
-
60
- $ curl http://localhost:8080 -d '{"name":"John Doe", "phone": "555 451-1234"}' | jq
61
- {
62
- "status": "success",
63
- "error": "",
64
- "data": {
65
- "id": "aea34022-4b75-4eed-ac92-65fa4f4511ae",
66
- "name": "John Doe",
67
- "phone": "5554511234"
68
- },
69
- "pagination": {},
70
- "input_errors": {}
71
- }
72
-
73
-
74
- $ curl http://localhost:8080 -d '{"name":"John Doe", "phone": "555 451-12341"}' | jq
75
- {
76
- "status": "input_errors",
77
- "error": "",
78
- "data": [],
79
- "pagination": {},
80
- "input_errors": {
81
- "phone": "Invalid phone number"
82
- }
83
- }
84
-
85
- $ curl http://localhost:8080 -d '{"name":"John Doe", "phone": "1-2-3-4 asdf"}' | jq
86
- {
87
- "status": "input_errors",
88
- "error": "",
89
- "data": [],
90
- "pagination": {},
91
- "input_errors": {
92
- "phone": "Invalid phone number"
93
- }
94
- }
95
- ```
96
- """
97
-
98
- """ Whether or not to allow non-USA numbers. """
99
- usa_only = configs.Boolean(default=True)
100
- _descriptor_config_map = None
101
-
102
- @clearskies.decorators.parameters_to_properties
103
- def __init__(
104
- self,
105
- usa_only: bool = True,
106
- default: str | None = None,
107
- setable: str | Callable[..., str] | None = None,
108
- is_readable: bool = True,
109
- is_writeable: bool = True,
110
- is_searchable: bool = True,
111
- is_temporary: bool = False,
112
- validators: clearskies.typing.validator | list[clearskies.typing.validator] = [],
113
- on_change_pre_save: clearskies.typing.action | list[clearskies.typing.action] = [],
114
- on_change_post_save: clearskies.typing.action | list[clearskies.typing.action] = [],
115
- on_change_save_finished: clearskies.typing.action | list[clearskies.typing.action] = [],
116
- created_by_source_type: str = "",
117
- created_by_source_key: str = "",
118
- created_by_source_strict: bool = True,
119
- ):
120
- pass
121
-
122
- def to_backend(self, data: dict[str, Any]) -> dict[str, Any]:
123
- if not data.get(self.name):
124
- return data
125
-
126
- # phone numbers are stored as only digits.
127
- return {**data, **{self.name: re.sub(r"\D", "", data[self.name])}}
128
-
129
- def input_error_for_value(self, value: str, operator: str | None = None) -> str:
130
- if type(value) != str:
131
- return f"Value must be a string for {self.name}"
132
-
133
- # we'll allow spaces, dashes, parenthesis, dashes, and plus signs.
134
- # if there is anything else then it's not a valid phone number.
135
- # However, we don't do more detailed validation, because I'm too lazy to
136
- # figure out what is and is not a valid phone number, especially when
137
- # you get to the world of international numbers.
138
- if re.search(r"[^\d \-()+]", value):
139
- return "Invalid phone number"
140
-
141
- # for some final validation (especially US numbers) work only with the digits.
142
- value = re.sub(r"\D", "", value)
143
-
144
- if len(value) > 15:
145
- return "Invalid phone number"
146
-
147
- # we can't be too short unless we're doing a fuzzy search
148
- if len(value) < 10 and operator and operator.lower() != "like":
149
- return "Invalid phone number"
150
-
151
- if self.usa_only:
152
- if len(value) > 11:
153
- return "Invalid phone number"
154
- if value[0] == "1" and len(value) != 11:
155
- return "Invalid phone number"
156
- if value[0] != "1" and len(value) != 10:
157
- return "Invalid phone number"
158
-
159
- return ""
@@ -1,92 +0,0 @@
1
- from typing import Callable
2
-
3
- import clearskies.decorators
4
- import clearskies.typing
5
- from clearskies import configs
6
- from clearskies.columns.string import String
7
-
8
-
9
- class Select(String):
10
- """
11
- A string column but, when writeable via an endpoint, only specific values are allowed.
12
-
13
- Note: the allowed values are case sensitive.
14
-
15
- ```python
16
- import clearskies
17
-
18
-
19
- class Order(clearskies.Model):
20
- id_column_name = "id"
21
- backend = clearskies.backends.MemoryBackend()
22
-
23
- id = clearskies.columns.Uuid()
24
- total = clearskies.columns.Float()
25
- status = clearskies.columns.Select(["Open", "Processing", "Shipped", "Complete"])
26
-
27
-
28
- wsgi = clearskies.contexts.WsgiRef(
29
- clearskies.endpoints.Create(
30
- Order,
31
- writeable_column_names=["total", "status"],
32
- readable_column_names=["id", "total", "status"],
33
- ),
34
- )
35
- wsgi()
36
- ```
37
-
38
- And when invoked:
39
-
40
- ```bash
41
- $ curl http://localhost:8080 -d '{"total": 125, "status": "Open"}' | jq
42
- {
43
- "status": "success",
44
- "error": "",
45
- "data": {
46
- "id": "22f2c950-6519-4d8e-9084-013455449b07",
47
- "total": 125.0,
48
- "status": "Open"
49
- },
50
- "pagination": {},
51
- "input_errors": {}
52
- }
53
-
54
- $ curl http://localhost:8080 -d '{"total": 125, "status": "huh"}' | jq
55
- {
56
- "status": "input_errors",
57
- "error": "",
58
- "data": [],
59
- "pagination": {},
60
- "input_errors": {
61
- "status": "Invalid value for status"
62
- }
63
- }
64
- ```
65
- """
66
-
67
- """ The allowed values. """
68
- allowed_values = configs.StringList(required=True)
69
- _descriptor_config_map = None
70
-
71
- @clearskies.decorators.parameters_to_properties
72
- def __init__(
73
- self,
74
- allowed_values: list[str],
75
- default: str | None = None,
76
- setable: str | Callable[..., str] | None = None,
77
- is_readable: bool = True,
78
- is_writeable: bool = True,
79
- is_searchable: bool = True,
80
- is_temporary: bool = False,
81
- validators: clearskies.typing.validator | list[clearskies.typing.validator] = [],
82
- on_change_pre_save: clearskies.typing.action | list[clearskies.typing.action] = [],
83
- on_change_post_save: clearskies.typing.action | list[clearskies.typing.action] = [],
84
- on_change_save_finished: clearskies.typing.action | list[clearskies.typing.action] = [],
85
- created_by_source_type: str = "",
86
- created_by_source_key: str = "",
87
- created_by_source_strict: bool = True,
88
- ):
89
- pass
90
-
91
- def input_error_for_value(self, value: str, operator: str | None = None) -> str:
92
- return f"Invalid value for {self.name}" if value not in self.allowed_values else ""
@@ -1,102 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import TYPE_CHECKING, Self, overload
4
-
5
- from clearskies.column import Column
6
-
7
- if TYPE_CHECKING:
8
- from clearskies import Model
9
-
10
-
11
- class String(Column):
12
- """
13
- A simple string column.
14
-
15
- ```python
16
- import clearskies
17
-
18
-
19
- class Pet(clearskies.Model):
20
- id_column_name = "id"
21
- backend = clearskies.backends.MemoryBackend()
22
-
23
- id = clearskies.columns.Uuid()
24
- name = clearskies.columns.String()
25
-
26
-
27
- wsgi = clearskies.contexts.WsgiRef(
28
- clearskies.endpoints.Create(
29
- Pet,
30
- writeable_column_names=["name"],
31
- readable_column_names=["id", "name"],
32
- ),
33
- )
34
- wsgi()
35
- ```
36
-
37
- And when invoked:
38
-
39
- ```bash
40
- $ curl http://localhost:8080 -d '{"name": "Spot"}' | jq
41
- {
42
- "status": "success",
43
- "error": "",
44
- "data": {
45
- "id": "e5b8417f-91bc-4fe5-9b64-04f571a7b10a",
46
- "name": "Spot"
47
- },
48
- "pagination": {},
49
- "input_errors": {}
50
- }
51
-
52
- $ curl http://localhost:8080 -d '{"name": 10}' | jq
53
- {
54
- "status": "input_errors",
55
- "error": "",
56
- "data": [],
57
- "pagination": {},
58
- "input_errors": {
59
- "name": "value should be a string"
60
- }
61
- }
62
-
63
- ```
64
- """
65
-
66
- _allowed_search_operators = ["<=>", "!=", "<=", ">=", ">", "<", "=", "in", "is not null", "is null", "like"]
67
- _descriptor_config_map = None
68
-
69
- @overload
70
- def __get__(self, instance: None, cls: type[Model]) -> Self:
71
- pass
72
-
73
- @overload
74
- def __get__(self, instance: Model, cls: type[Model]) -> str:
75
- pass
76
-
77
- def __get__(self, instance, cls):
78
- if instance is None:
79
- self.model_class = cls
80
- return self
81
-
82
- # this makes sure we're initialized
83
- if "name" not in self._config: # type: ignore
84
- instance.get_columns()
85
-
86
- if self.name not in instance._data:
87
- return None # type: ignore
88
-
89
- if self.name not in instance._transformed_data:
90
- instance._transformed_data[self.name] = self.from_backend(instance._data[self.name])
91
-
92
- return instance._transformed_data[self.name]
93
-
94
- def __set__(self, instance: Model, value: str) -> None:
95
- # this makes sure we're initialized
96
- if "name" not in self._config: # type: ignore
97
- instance.get_columns()
98
-
99
- instance._next_data[self.name] = value
100
-
101
- def input_error_for_value(self, value: str, operator: str | None = None) -> str:
102
- return "value should be a string" if type(value) != str else ""
@@ -1,164 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import datetime
4
- from typing import TYPE_CHECKING, Any, Callable, Self, Type, overload
5
-
6
- import clearskies.decorators
7
- import clearskies.typing
8
- from clearskies import configs
9
- from clearskies.columns.datetime import Datetime
10
-
11
- if TYPE_CHECKING:
12
- from clearskies import Model
13
-
14
-
15
- class Timestamp(Datetime):
16
- """
17
- A timestamp column.
18
-
19
- The difference between this and the datetime column is that this stores the datetime
20
- as a standard unix timestamp - the number of seconds since the unix epoch.
21
-
22
- Also, this **always** assumes the timezone for the timestamp is UTC
23
-
24
- ```python
25
- import datetime
26
- import clearskies
27
-
28
-
29
- class Pet(clearskies.Model):
30
- id_column_name = "id"
31
- backend = clearskies.backends.MemoryBackend()
32
-
33
- id = clearskies.columns.Uuid()
34
- name = clearskies.columns.String()
35
- last_fed = clearskies.columns.Timestamp()
36
-
37
-
38
- def demo_timestamp(utcnow: datetime.datetime, pets: Pet) -> dict[str, str | int]:
39
- pet = pets.create({
40
- "name": "Spot",
41
- "last_fed": utcnow,
42
- })
43
- return {
44
- "last_fed": pet.last_fed.isoformat(),
45
- "raw_data": pet.get_raw_data()["last_fed"],
46
- }
47
-
48
-
49
- cli = clearskies.contexts.Cli(
50
- clearskies.endpoints.Callable(
51
- demo_timestamp,
52
- ),
53
- classes=[Pet],
54
- )
55
- cli()
56
- ```
57
-
58
- And when invoked it returns:
59
-
60
- ```json
61
- {
62
- "status": "success",
63
- "error": "",
64
- "data": {"last_fed": "2025-05-18T19:14:56+00:00", "raw_data": 1747595696},
65
- "pagination": {},
66
- "input_errors": {},
67
- }
68
- ```
69
-
70
- Note that if you pull the column from the model in the usual way (e.g. `pet.last_fed` you get a timestamp,
71
- but if you check the raw data straight out of the backend (e.g. `pet.get_raw_data()["last_fed"]`) it's an
72
- integer.
73
- """
74
-
75
- # whether or not to include the microseconds in the timestamp
76
- include_microseconds = configs.Boolean(default=False)
77
- _descriptor_config_map = None
78
-
79
- @clearskies.decorators.parameters_to_properties
80
- def __init__(
81
- self,
82
- include_microseconds: bool = False,
83
- default: datetime.datetime | None = None,
84
- setable: datetime.datetime | Callable[..., datetime.datetime] | None = None,
85
- is_readable: bool = True,
86
- is_writeable: bool = True,
87
- is_searchable: bool = True,
88
- is_temporary: bool = False,
89
- validators: clearskies.typing.validator | list[clearskies.typing.validator] = [],
90
- on_change_pre_save: clearskies.typing.action | list[clearskies.typing.action] = [],
91
- on_change_post_save: clearskies.typing.action | list[clearskies.typing.action] = [],
92
- on_change_save_finished: clearskies.typing.action | list[clearskies.typing.action] = [],
93
- created_by_source_type: str = "",
94
- created_by_source_key: str = "",
95
- created_by_source_strict: bool = True,
96
- ):
97
- pass
98
-
99
- def from_backend(self, value) -> datetime.datetime | None:
100
- mult = 1000 if self.include_microseconds else 1
101
- if not value:
102
- date = None
103
- elif isinstance(value, str):
104
- if not value.isdigit():
105
- raise ValueError(
106
- f"Invalid data was found in the backend for model {self.model_class.__name__} and column {self.name}: a string value was found that is not a timestamp. It was '{value}'"
107
- )
108
- date = datetime.datetime.fromtimestamp(int(value) / mult, datetime.timezone.utc)
109
- elif isinstance(value, int) or isinstance(value, float):
110
- date = datetime.datetime.fromtimestamp(value / mult, datetime.timezone.utc)
111
- else:
112
- if not isinstance(value, datetime.datetime):
113
- raise ValueError(
114
- f"Invalid data was found in the backend for model {self.model_class.__name__} and column {self.name}: the value was neither an integer, float, string, or datetime object"
115
- )
116
- date = value
117
- return date.replace(tzinfo=datetime.timezone.utc) if date else None
118
-
119
- def to_backend(self, data: dict[str, Any]) -> dict[str, Any]:
120
- if not self.name in data or isinstance(data[self.name], int) or data[self.name] == None:
121
- return data
122
-
123
- value = data[self.name]
124
- if isinstance(value, str):
125
- if not value.isdigit():
126
- raise ValueError(
127
- f"Invalid data was sent to the backend for model {self.model_class.__name__} and column {self.name}: a string value was found that is not a timestamp. It was '{value}'"
128
- )
129
- value = int(value)
130
- elif isinstance(value, datetime.datetime):
131
- value = value.timestamp()
132
- else:
133
- raise ValueError(
134
- f"Invalid data was sent to the backend for model {self.model_class.__name__} and column {self.name}: the value was neither an integer, a string, nor a datetime object"
135
- )
136
-
137
- return {**data, self.name: int(value)}
138
-
139
- @overload
140
- def __get__(self, instance: None, cls: type) -> Self:
141
- pass
142
-
143
- @overload
144
- def __get__(self, instance: Model, cls: type) -> datetime.datetime:
145
- pass
146
-
147
- def __get__(self, instance, cls):
148
- return super().__get__(instance, cls)
149
-
150
- def __set__(self, instance, value: datetime.datetime) -> None:
151
- # this makes sure we're initialized
152
- if "name" not in self._config: # type: ignore
153
- instance.get_columns()
154
-
155
- instance._next_data[self.name] = value
156
-
157
- def input_error_for_value(self, value: str, operator: str | None = None) -> str:
158
- if not isinstance(value, int):
159
- return f"'{self.name}' must be an integer"
160
- return ""
161
-
162
- def values_match(self, value_1, value_2):
163
- """Compare two values to see if they are the same."""
164
- return value_1 == value_2
@@ -1,110 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import datetime
4
- from typing import TYPE_CHECKING, Any
5
-
6
- import clearskies.decorators
7
- import clearskies.di
8
- import clearskies.typing
9
- from clearskies import configs
10
- from clearskies.columns.datetime import Datetime
11
-
12
- if TYPE_CHECKING:
13
- from clearskies import Model
14
-
15
-
16
- class Updated(Datetime):
17
- """
18
- The updated column records the time that a record is created or updated.
19
-
20
- Note that this will always populate the column anytime the model is created or updated.
21
- You don't have to provide the timestamp yourself and you should never expose it as
22
- a writeable column through an endpoint (in fact, you can't).
23
-
24
- ```python
25
- import clearskies
26
- import time
27
-
28
-
29
- class MyModel(clearskies.Model):
30
- backend = clearskies.backends.MemoryBackend()
31
- id_column_name = "id"
32
-
33
- id = clearskies.columns.Uuid()
34
- name = clearskies.columns.String()
35
- created = clearskies.columns.Created()
36
- updated = clearskies.columns.Updated()
37
-
38
-
39
- def test_updated(my_models: MyModel) -> MyModel:
40
- my_model = my_models.create({"name": "Jane"})
41
- updated_column_after_create = my_model.updated
42
-
43
- time.sleep(2)
44
-
45
- my_model.save({"name": "Susan"})
46
-
47
- return {
48
- "updated_column_after_create": updated_column_after_create.isoformat(),
49
- "updated_column_at_end": my_model.updated.isoformat(),
50
- "difference_in_seconds": (my_model.updated - updated_column_after_create).total_seconds(),
51
- }
52
-
53
-
54
- cli = clearskies.contexts.Cli(clearskies.endpoints.Callable(test_updated), classes=[MyModel])
55
- cli()
56
- ```
57
-
58
- And when invoked:
59
-
60
- ```bash
61
- $ ./test.py | jq
62
- {
63
- "status": "success",
64
- "error": "",
65
- "data": {
66
- "updated_column_after_create": "2025-05-18T19:28:46+00:00",
67
- "updated_column_at_end": "2025-05-18T19:28:48+00:00",
68
- "difference_in_seconds": 2.0
69
- },
70
- "pagination": {},
71
- "input_errors": {}
72
- }
73
- ```
74
-
75
- Note that the `updated` column was set both when the record was first created and when it was updated,
76
- so there is a two second difference between them (since we slept for two seconds).
77
-
78
- """
79
-
80
- """
81
- Created fields are never writeable because they always set the created time automatically.
82
- """
83
- is_writeable = configs.Boolean(default=False)
84
- _descriptor_config_map = None
85
-
86
- now = clearskies.di.inject.Now()
87
-
88
- @clearskies.decorators.parameters_to_properties
89
- def __init__(
90
- self,
91
- in_utc: bool = True,
92
- date_format: str = "%Y-%m-%d %H:%M:%S",
93
- backend_default: str = "0000-00-00 00:00:00",
94
- is_readable: bool = True,
95
- is_searchable: bool = True,
96
- is_temporary: bool = False,
97
- on_change_pre_save: clearskies.typing.action | list[clearskies.typing.action] = [],
98
- on_change_post_save: clearskies.typing.action | list[clearskies.typing.action] = [],
99
- on_change_save_finished: clearskies.typing.action | list[clearskies.typing.action] = [],
100
- ):
101
- pass
102
-
103
- def pre_save(self, data: dict[str, Any], model: Model) -> dict[str, Any]:
104
- now = self.now
105
- if self.timezone_aware:
106
- now = now.astimezone(self.timezone)
107
- data = {**data, self.name: now}
108
- if self.on_change_pre_save:
109
- data = self.execute_actions_with_data(self.on_change_pre_save, model, data)
110
- return data