clear-skies 2.0.7__py3-none-any.whl → 2.0.8__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 (148) hide show
  1. {clear_skies-2.0.7.dist-info → clear_skies-2.0.8.dist-info}/METADATA +1 -1
  2. clear_skies-2.0.8.dist-info/RECORD +252 -0
  3. clearskies/__init__.py +2 -2
  4. clearskies/authentication/authentication.py +1 -3
  5. clearskies/authentication/authorization.py +12 -5
  6. clearskies/authentication/authorization_pass_through.py +5 -3
  7. clearskies/authentication/jwks.py +25 -23
  8. clearskies/authentication/secret_bearer.py +15 -17
  9. clearskies/autodoc/schema/schema.py +1 -1
  10. clearskies/backends/api_backend.py +50 -56
  11. clearskies/backends/backend.py +14 -14
  12. clearskies/backends/cursor_backend.py +17 -23
  13. clearskies/backends/memory_backend.py +27 -30
  14. clearskies/backends/secrets_backend.py +13 -18
  15. clearskies/column.py +44 -56
  16. clearskies/columns/audit.py +14 -13
  17. clearskies/columns/belongs_to_id.py +10 -15
  18. clearskies/columns/belongs_to_model.py +6 -9
  19. clearskies/columns/belongs_to_self.py +13 -9
  20. clearskies/columns/boolean.py +13 -16
  21. clearskies/columns/category_tree.py +9 -11
  22. clearskies/columns/category_tree_children.py +2 -3
  23. clearskies/columns/category_tree_descendants.py +1 -1
  24. clearskies/columns/created.py +8 -11
  25. clearskies/columns/created_by_authorization_data.py +7 -9
  26. clearskies/columns/created_by_header.py +12 -8
  27. clearskies/columns/created_by_ip.py +6 -8
  28. clearskies/columns/created_by_routing_data.py +12 -7
  29. clearskies/columns/created_by_user_agent.py +6 -9
  30. clearskies/columns/date.py +12 -14
  31. clearskies/columns/datetime.py +19 -17
  32. clearskies/columns/email.py +3 -1
  33. clearskies/columns/float.py +10 -14
  34. clearskies/columns/has_many.py +8 -10
  35. clearskies/columns/has_many_self.py +13 -7
  36. clearskies/columns/has_one.py +2 -0
  37. clearskies/columns/integer.py +9 -11
  38. clearskies/columns/json.py +10 -12
  39. clearskies/columns/many_to_many_ids.py +14 -16
  40. clearskies/columns/many_to_many_ids_with_data.py +16 -16
  41. clearskies/columns/many_to_many_models.py +5 -7
  42. clearskies/columns/many_to_many_pivots.py +3 -5
  43. clearskies/columns/phone.py +12 -9
  44. clearskies/columns/select.py +12 -9
  45. clearskies/columns/string.py +1 -1
  46. clearskies/columns/timestamp.py +15 -15
  47. clearskies/columns/updated.py +8 -10
  48. clearskies/columns/uuid.py +7 -10
  49. clearskies/configs/any.py +2 -0
  50. clearskies/configs/any_dict.py +2 -0
  51. clearskies/configs/any_dict_or_callable.py +2 -0
  52. clearskies/configs/boolean.py +2 -0
  53. clearskies/configs/boolean_or_callable.py +2 -0
  54. clearskies/configs/callable_config.py +2 -0
  55. clearskies/configs/config.py +2 -0
  56. clearskies/configs/datetime.py +2 -0
  57. clearskies/configs/datetime_or_callable.py +2 -0
  58. clearskies/configs/float.py +2 -0
  59. clearskies/configs/float_or_callable.py +2 -0
  60. clearskies/configs/integer.py +2 -0
  61. clearskies/configs/integer_or_callable.py +2 -0
  62. clearskies/configs/list_any_dict.py +2 -0
  63. clearskies/configs/list_any_dict_or_callable.py +2 -0
  64. clearskies/configs/model_column.py +2 -0
  65. clearskies/configs/model_columns.py +2 -0
  66. clearskies/configs/model_destination_name.py +2 -1
  67. clearskies/configs/model_to_id_column.py +2 -0
  68. clearskies/configs/readable_model_column.py +2 -0
  69. clearskies/configs/readable_model_columns.py +2 -0
  70. clearskies/configs/searchable_model_columns.py +2 -0
  71. clearskies/configs/select.py +2 -0
  72. clearskies/configs/select_list.py +2 -0
  73. clearskies/configs/string.py +2 -0
  74. clearskies/configs/string_dict.py +2 -0
  75. clearskies/configs/string_list.py +2 -0
  76. clearskies/configs/string_list_or_callable.py +2 -0
  77. clearskies/configs/timedelta.py +2 -0
  78. clearskies/configs/timezone.py +2 -0
  79. clearskies/configs/url.py +2 -0
  80. clearskies/configs/writeable_model_column.py +2 -0
  81. clearskies/configs/writeable_model_columns.py +2 -0
  82. clearskies/configurable.py +2 -0
  83. clearskies/contexts/cli.py +9 -1
  84. clearskies/contexts/context.py +13 -14
  85. clearskies/contexts/wsgi.py +12 -10
  86. clearskies/contexts/wsgi_ref.py +12 -6
  87. clearskies/decorators.py +1 -1
  88. clearskies/decorators.pyi +10 -0
  89. clearskies/di/di.py +7 -6
  90. clearskies/di/inject/by_class.py +2 -0
  91. clearskies/di/inject/by_name.py +2 -0
  92. clearskies/di/inject/di.py +2 -0
  93. clearskies/di/inject/environment.py +1 -1
  94. clearskies/di/inject/now.py +2 -0
  95. clearskies/di/inject/requests.py +2 -0
  96. clearskies/di/inject/secrets.py +2 -2
  97. clearskies/di/inject/utcnow.py +2 -0
  98. clearskies/di/inject/uuid.py +2 -2
  99. clearskies/end.py +45 -7
  100. clearskies/endpoint.py +43 -59
  101. clearskies/endpoint_group.py +15 -18
  102. clearskies/endpoints/advanced_search.py +19 -26
  103. clearskies/endpoints/callable.py +10 -16
  104. clearskies/endpoints/create.py +6 -10
  105. clearskies/endpoints/delete.py +5 -11
  106. clearskies/endpoints/get.py +11 -15
  107. clearskies/endpoints/health_check.py +9 -11
  108. clearskies/endpoints/list.py +29 -36
  109. clearskies/endpoints/restful_api.py +43 -53
  110. clearskies/endpoints/schema.py +14 -18
  111. clearskies/endpoints/simple_search.py +5 -12
  112. clearskies/endpoints/update.py +6 -11
  113. clearskies/environment.py +2 -0
  114. clearskies/input_outputs/cli.py +2 -0
  115. clearskies/input_outputs/headers.py +2 -0
  116. clearskies/input_outputs/input_output.py +15 -15
  117. clearskies/input_outputs/programmatic.py +2 -2
  118. clearskies/input_outputs/wsgi.py +2 -2
  119. clearskies/model.py +120 -25
  120. clearskies/query/query.py +1 -4
  121. clearskies/secrets/__init__.py +2 -1
  122. clearskies/secrets/akeyless.py +12 -10
  123. clearskies/secrets/secrets.py +7 -2
  124. clearskies/security_header.py +4 -2
  125. clearskies/security_headers/cache_control.py +15 -14
  126. clearskies/security_headers/cors.py +10 -9
  127. clearskies/security_headers/csp.py +25 -24
  128. clearskies/security_headers/hsts.py +6 -5
  129. clearskies/typing.py +1 -1
  130. clearskies/validator.py +5 -6
  131. clearskies/validators/after_column.py +6 -7
  132. clearskies/validators/before_column.py +2 -0
  133. clearskies/validators/in_the_future.py +5 -8
  134. clearskies/validators/in_the_future_at_least.py +2 -0
  135. clearskies/validators/in_the_future_at_most.py +2 -0
  136. clearskies/validators/in_the_past.py +5 -8
  137. clearskies/validators/in_the_past_at_least.py +2 -0
  138. clearskies/validators/in_the_past_at_most.py +2 -0
  139. clearskies/validators/maximum_length.py +4 -5
  140. clearskies/validators/maximum_value.py +4 -4
  141. clearskies/validators/minimum_length.py +4 -4
  142. clearskies/validators/minimum_value.py +4 -4
  143. clearskies/validators/required.py +2 -4
  144. clearskies/validators/timedelta.py +8 -9
  145. clearskies/validators/unique.py +2 -3
  146. clear_skies-2.0.7.dist-info/RECORD +0 -251
  147. {clear_skies-2.0.7.dist-info → clear_skies-2.0.8.dist-info}/WHEEL +0 -0
  148. {clear_skies-2.0.7.dist-info → clear_skies-2.0.8.dist-info}/licenses/LICENSE +0 -0
clearskies/end.py CHANGED
@@ -1,17 +1,22 @@
1
- # type: ignore
2
1
  from __future__ import annotations
3
2
 
4
3
  from abc import ABC
5
4
  from typing import TYPE_CHECKING, Any
6
5
 
7
6
  if TYPE_CHECKING:
8
- from clearskies.input_output import InputOutput
7
+ from clearskies import typing
8
+ from clearskies.input_outputs import InputOutput
9
9
 
10
- from clearskies import exceptions
10
+ from clearskies import configs, configurable, di, exceptions
11
+ from clearskies.authentication import Authorization, Public
11
12
  from clearskies.functional import string
12
13
 
13
14
 
14
- class End(ABC):
15
+ class End(
16
+ ABC,
17
+ configurable.Configurable,
18
+ di.InjectableProperties,
19
+ ):
15
20
  """
16
21
  DRY for endpoint and endpoint groups.
17
22
 
@@ -20,6 +25,24 @@ class End(ABC):
20
25
  from the other.
21
26
  """
22
27
 
28
+ di = di.inject.Di()
29
+
30
+ url = configs.String(required=False, default="")
31
+
32
+ authentication = configs.Authentication(default=None)
33
+
34
+ authorization = configs.Authorization(default=None)
35
+
36
+ internal_casing = configs.Select(["snake_case", "camelCase", "TitleCase"], default="snake_case")
37
+ external_casing = configs.Select(["snake_case", "camelCase", "TitleCase"], default="snake_case")
38
+ response_headers = configs.StringListOrCallable(default=[])
39
+ authentication = configs.Authentication(default=Public())
40
+ authorization = configs.Authorization(default=Authorization())
41
+ security_headers = configs.SecurityHeaders(default=[])
42
+
43
+ cors_header: SecurityHeader = None # type: ignore
44
+ has_cors: bool = False
45
+
23
46
  def add_url_prefix(self, prefix: str) -> None:
24
47
  self.url = (prefix.rstrip("/") + "/" + self.url.lstrip("/")).lstrip("/")
25
48
 
@@ -41,7 +64,7 @@ class End(ABC):
41
64
  if not self.authorization.gate(input_output.authorization_data, input_output):
42
65
  raise exceptions.Authorization("Not Authorized")
43
66
  except exceptions.ClientError as client_error:
44
- raise exception.Authorization(str(client_error))
67
+ raise exceptions.Authorization(str(client_error))
45
68
 
46
69
  def __call__(self, input_output: InputOutput) -> Any:
47
70
  """
@@ -116,7 +139,7 @@ class End(ABC):
116
139
  for index, response_header in enumerate(response_headers):
117
140
  if not isinstance(response_header, str):
118
141
  raise TypeError(
119
- f"Invalid response header in entry #{index + 1}: the header should be a string, but I was given a type of '{header.__class__.__name__}' instead."
142
+ f"Invalid response header in entry #{index + 1}: the header should be a string, but I was given a type of '{response_header.__class__.__name__}' instead."
120
143
  )
121
144
  parts = response_header.split(":", 1)
122
145
  if len(parts) != 2:
@@ -127,7 +150,7 @@ class End(ABC):
127
150
  for security_header in self.security_headers:
128
151
  security_header.set_headers_for_input_output(input_output)
129
152
 
130
- def respond(self, input_output: InputOutput, response: clearskies.typing.response, status_code: int) -> Any:
153
+ def respond(self, input_output: InputOutput, response: typing.response, status_code: int) -> Any:
131
154
  self.add_response_headers(input_output)
132
155
  return input_output.respond(response, status_code)
133
156
 
@@ -181,3 +204,18 @@ class End(ABC):
181
204
  self.external_casing,
182
205
  self.internal_casing,
183
206
  )
207
+
208
+ def error(self, input_output: InputOutput, message: str, status_code: int) -> Any:
209
+ """Return a client-side error (e.g. 400)."""
210
+ return self.respond_json(input_output, {"status": "client_error", "error": message}, status_code)
211
+
212
+ def input_errors(self, input_output: InputOutput, errors: dict[str, str], status_code: int = 200) -> Any:
213
+ """Return input errors to the client."""
214
+ return self.respond_json(input_output, {"status": "input_errors", "input_errors": errors}, status_code)
215
+
216
+ def redirect(self, input_output: InputOutput, location: str, status_code: int) -> Any:
217
+ """Return a redirect response (e.g. 302)."""
218
+ return self.respond_json(input_output, {"status": "redirect", "location": location}, status_code)
219
+
220
+ def handle(self, input_output: InputOutput) -> Any:
221
+ raise NotImplementedError()
clearskies/endpoint.py CHANGED
@@ -3,16 +3,9 @@ from __future__ import annotations
3
3
  import inspect
4
4
  import urllib.parse
5
5
  from collections import OrderedDict
6
- from typing import TYPE_CHECKING, Any, Callable
7
-
8
- import clearskies.column
9
- import clearskies.configs
10
- import clearskies.configurable
11
- import clearskies.decorators
12
- import clearskies.di
13
- import clearskies.end
14
- import clearskies.typing
15
- from clearskies import autodoc, exceptions
6
+ from typing import TYPE_CHECKING, Any, Callable, Optional
7
+
8
+ from clearskies import autodoc, column, configs, configurable, decorators, di, end, exceptions
16
9
  from clearskies.authentication import Authentication, Authorization, Public
17
10
  from clearskies.autodoc import schema
18
11
  from clearskies.autodoc.request import Parameter, Request
@@ -27,12 +20,12 @@ if TYPE_CHECKING:
27
20
 
28
21
 
29
22
  class Endpoint(
30
- clearskies.end.End, # type: ignore
31
- clearskies.configurable.Configurable,
32
- clearskies.di.InjectableProperties,
23
+ end.End, # type: ignore
24
+ configurable.Configurable,
25
+ di.InjectableProperties,
33
26
  ):
34
27
  """
35
- Automating drudgery!
28
+ Automating drudgery.
36
29
 
37
30
  With clearskies, endpoints exist to offload some drudgery and make your life easier, but they can also
38
31
  get out of your way when you don't need them. Think of them as pre-built endpoints that can execute
@@ -47,7 +40,7 @@ class Endpoint(
47
40
  """
48
41
  The dependency injection container
49
42
  """
50
- di = clearskies.di.inject.Di()
43
+ di = di.inject.Di()
51
44
 
52
45
  """
53
46
  Whether or not this endpoint can handle CORS
@@ -57,7 +50,7 @@ class Endpoint(
57
50
  """
58
51
  The actual CORS header
59
52
  """
60
- cors_header: Cors | None = None
53
+ cors_header: Optional[Cors] = None
61
54
 
62
55
  """
63
56
  Set some response headers that should be returned for this endpoint.
@@ -82,7 +75,7 @@ class Endpoint(
82
75
  wsgi()
83
76
  ```
84
77
  """
85
- response_headers = clearskies.configs.StringListOrCallable(default=[])
78
+ response_headers = configs.StringListOrCallable(default=[])
86
79
 
87
80
  """
88
81
  Set the URL for the endpoint
@@ -161,7 +154,7 @@ class Endpoint(
161
154
  ```
162
155
 
163
156
  """
164
- url = clearskies.configs.Url(default="")
157
+ url = configs.Url(default="")
165
158
 
166
159
  """
167
160
  The allowed request methods for this endpoint.
@@ -204,7 +197,7 @@ class Endpoint(
204
197
  }
205
198
  ```
206
199
  """
207
- request_methods = clearskies.configs.SelectList(
200
+ request_methods = configs.SelectList(
208
201
  allowed_values=["GET", "POST", "PUT", "DELETE", "PATCH", "QUERY"], default=["GET"]
209
202
  )
210
203
 
@@ -214,7 +207,7 @@ class Endpoint(
214
207
  Use this to attach an instance of `clearskies.authentication.Authentication` to an endpoint, which enforces authentication.
215
208
  For more details, see the dedicated documentation section on authentication and authorization. By default, all endpoints are public.
216
209
  """
217
- authentication = clearskies.configs.Authentication(default=Public())
210
+ authentication = configs.Authentication(default=Public())
218
211
 
219
212
  """
220
213
  The authorization rules for this endpoint
@@ -222,7 +215,7 @@ class Endpoint(
222
215
  Use this to attach an instance of `clearskies.authentication.Authorization` to an endpoint, which enforces authorization.
223
216
  For more details, see the dedicated documentation section on authentication and authorization. By default, no authorization is enforced.
224
217
  """
225
- authorization = clearskies.configs.Authorization(default=Authorization())
218
+ authorization = configs.Authorization(default=Authorization())
226
219
 
227
220
  """
228
221
  An override of the default model-to-json mapping for endpoints that auto-convert models to json.
@@ -329,7 +322,7 @@ class Endpoint(
329
322
  ```
330
323
 
331
324
  """
332
- output_map = clearskies.configs.Callable(default=None)
325
+ output_map = configs.Callable(default=None)
333
326
 
334
327
  """
335
328
  A schema that describes the expected output to the client.
@@ -338,14 +331,14 @@ class Endpoint(
338
331
  Note that this is typically not required - when returning models and relying on clearskies to auto-convert to JSON,
339
332
  it will also automatically generate your documentation.
340
333
  """
341
- output_schema = clearskies.configs.Schema(default=None)
334
+ output_schema = configs.Schema(default=None)
342
335
 
343
336
  """
344
337
  The model class used by this endpoint.
345
338
 
346
339
  The endpoint will use this to fetch/save/validate incoming data as needed.
347
340
  """
348
- model_class = clearskies.configs.ModelClass(default=None)
341
+ model_class = configs.ModelClass(default=None)
349
342
 
350
343
  """
351
344
  Columns from the model class that should be returned to the client.
@@ -421,7 +414,7 @@ class Endpoint(
421
414
 
422
415
  ```
423
416
  """
424
- readable_column_names = clearskies.configs.ReadableModelColumns("model_class", default=[])
417
+ readable_column_names = configs.ReadableModelColumns("model_class", default=[])
425
418
 
426
419
  """
427
420
  Specifies which columns from a model class can be set by the client.
@@ -486,14 +479,14 @@ class Endpoint(
486
479
  ```
487
480
 
488
481
  """
489
- writeable_column_names = clearskies.configs.WriteableModelColumns("model_class", default=[])
482
+ writeable_column_names = configs.WriteableModelColumns("model_class", default=[])
490
483
 
491
484
  """
492
485
  Columns from the model class that can be searched by the client.
493
486
 
494
487
  Sets which columns the client is allowed to search (for endpoints that support searching).
495
488
  """
496
- searchable_column_names = clearskies.configs.SearchableModelColumns("model_class", default=[])
489
+ searchable_column_names = configs.SearchableModelColumns("model_class", default=[])
497
490
 
498
491
  """
499
492
  A function to call to add custom input validation logic.
@@ -557,7 +550,7 @@ class Endpoint(
557
550
  ```
558
551
 
559
552
  """
560
- input_validation_callable = clearskies.configs.Callable(default=None)
553
+ input_validation_callable = configs.Callable(default=None)
561
554
 
562
555
  """
563
556
  A dictionary with columns that should override columns in the model.
@@ -579,7 +572,7 @@ class Endpoint(
579
572
  )
580
573
  ```
581
574
  """
582
- column_overrides = clearskies.configs.Columns(default={})
575
+ column_overrides = configs.Columns(default={})
583
576
 
584
577
  """
585
578
  Used in conjunction with external_casing to change the casing of the key names in the outputted JSON of the endpoint.
@@ -638,14 +631,14 @@ class Endpoint(
638
631
  }
639
632
  ```
640
633
  """
641
- internal_casing = clearskies.configs.Select(["snake_case", "camelCase", "TitleCase"], default="snake_case")
634
+ internal_casing = configs.Select(["snake_case", "camelCase", "TitleCase"], default="snake_case")
642
635
 
643
636
  """
644
637
  Used in conjunction with internal_casing to change the casing of the key names in the outputted JSON of the endpoint.
645
638
 
646
639
  See the docs for `internal_casing` for more details and usage examples.
647
640
  """
648
- external_casing = clearskies.configs.Select(["snake_case", "camelCase", "TitleCase"], default="snake_case")
641
+ external_casing = configs.Select(["snake_case", "camelCase", "TitleCase"], default="snake_case")
649
642
 
650
643
  """
651
644
  Configure standard security headers to be sent along in the response from this endpoint.
@@ -689,19 +682,19 @@ class Endpoint(
689
682
  ```
690
683
 
691
684
  """
692
- security_headers = clearskies.configs.SecurityHeaders(default=[])
685
+ security_headers = configs.SecurityHeaders(default=[])
693
686
 
694
687
  """
695
688
  A description for this endpoint. This is added to any auto-documentation
696
689
  """
697
- description = clearskies.configs.String(default="")
690
+ description = configs.String(default="")
698
691
 
699
692
  """
700
693
  Whether or not the routing data should also be persisted to the model. Defaults to False.
701
694
 
702
695
  Note: this is only relevant for handlers that accept request data
703
696
  """
704
- include_routing_data_in_request_data = clearskies.configs.Boolean(default=False)
697
+ include_routing_data_in_request_data = configs.Boolean(default=False)
705
698
 
706
699
  """
707
700
  Additional conditions to always add to the results.
@@ -783,7 +776,7 @@ class Endpoint(
783
776
  and note that neither Greg nor Ann are returned. Ann because she doesn't make the grade criteria, and Greg because
784
777
  he won't graduate.
785
778
  """
786
- where = clearskies.configs.Conditions(default=[])
779
+ where = configs.Conditions(default=[])
787
780
 
788
781
  """
789
782
  Additional joins to always add to the query.
@@ -868,18 +861,18 @@ class Endpoint(
868
861
  e.g., the inner join reomves all the students that don't have an entry in the PastRecord model.
869
862
 
870
863
  """
871
- joins = clearskies.configs.Joins(default=[])
864
+ joins = configs.Joins(default=[])
872
865
 
873
866
  cors_header: Cors = None # type: ignore
874
- _model: clearskies.model.Model = None # type: ignore
875
- _columns: dict[str, clearskies.column.Column] = None # type: ignore
876
- _readable_columns: dict[str, clearskies.column.Column] = None # type: ignore
877
- _writeable_columns: dict[str, clearskies.column.Column] = None # type: ignore
878
- _searchable_columns: dict[str, clearskies.column.Column] = None # type: ignore
879
- _sortable_columns: dict[str, clearskies.column.Column] = None # type: ignore
880
- _as_json_map: dict[str, clearskies.column.Column] = None # type: ignore
881
-
882
- @clearskies.decorators.parameters_to_properties
867
+ _model: Model = None # type: ignore
868
+ _columns: dict[str, column.Column] = None # type: ignore
869
+ _readable_columns: dict[str, column.Column] = None # type: ignore
870
+ _writeable_columns: dict[str, column.Column] = None # type: ignore
871
+ _searchable_columns: dict[str, column.Column] = None # type: ignore
872
+ _sortable_columns: dict[str, column.Column] = None # type: ignore
873
+ _as_json_map: dict[str, column.Column] = None # type: ignore
874
+
875
+ @decorators.parameters_to_properties
883
876
  def __init__(
884
877
  self,
885
878
  url: str = "",
@@ -972,9 +965,6 @@ class Endpoint(
972
965
  )
973
966
  return self.authorization.filter_model(model, input_output.authorization_data, input_output)
974
967
 
975
- def handle(self, input_output: InputOutput) -> Any:
976
- raise NotImplementedError()
977
-
978
968
  def matches_request(self, input_output: InputOutput, allow_partial=False) -> bool:
979
969
  """Whether or not we can handle an incoming request based on URL and request method."""
980
970
  # soo..... this excessively duplicates the logic in __call__, but I'm being lazy right now
@@ -1012,20 +1002,14 @@ class Endpoint(
1012
1002
  def failure(self, input_output: InputOutput) -> Any:
1013
1003
  return self.respond_json(input_output, {"status": "failure"}, 500)
1014
1004
 
1015
- def input_errors(self, input_output: InputOutput, errors: dict[str, str], status_code: int = 200) -> Any:
1016
- """Return input errors to the client."""
1017
- return self.respond_json(input_output, {"status": "input_errors", "input_errors": errors}, status_code)
1018
-
1019
- def error(self, input_output: InputOutput, message: str, status_code: int) -> Any:
1020
- """Return a client-side error (e.g. 400)."""
1021
- return self.respond_json(input_output, {"status": "client_error", "error": message}, status_code)
1022
-
1023
1005
  def redirect(self, input_output: InputOutput, location: str, status_code: int) -> Any:
1024
1006
  """Return a redirect."""
1025
1007
  input_output.response_headers.add("content-type", "text/html")
1026
1008
  input_output.response_headers.add("location", location)
1027
1009
  return self.respond(
1028
- '<meta http-equiv="refresh" content="0; url=' + urllib.parse.quote(location) + '">Redirecting', status_code
1010
+ input_output,
1011
+ '<meta http-equiv="refresh" content="0; url=' + urllib.parse.quote(location) + '">Redirecting',
1012
+ status_code,
1029
1013
  )
1030
1014
 
1031
1015
  def success(
@@ -1053,7 +1037,7 @@ class Endpoint(
1053
1037
 
1054
1038
  return self.respond_json(input_output, response_data, 200)
1055
1039
 
1056
- def model_as_json(self, model: clearskies.model.Model, input_output: InputOutput) -> dict[str, Any]:
1040
+ def model_as_json(self, model: Model, input_output: InputOutput) -> dict[str, Any]:
1057
1041
  if self.output_map:
1058
1042
  return self.di.call_function(self.output_map, model=model, **input_output.get_context_for_callables())
1059
1043
 
@@ -1070,7 +1054,7 @@ class Endpoint(
1070
1054
  json[self.auto_case_column_name(key, True)] = value
1071
1055
  return json
1072
1056
 
1073
- def _build_as_json_map(self, model: clearskies.model.Model) -> dict[str, clearskies.column.Column]:
1057
+ def _build_as_json_map(self, model: Model) -> dict[str, column.Column]:
1074
1058
  conversion_map = {}
1075
1059
  if not self.readable_column_names:
1076
1060
  raise ValueError(
@@ -2,10 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from typing import TYPE_CHECKING, Any, Callable, Self
4
4
 
5
- import clearskies.configurable
6
- import clearskies.di
7
- import clearskies.end
8
- from clearskies import exceptions
5
+ from clearskies import configs, configurable, decorators, di, end
9
6
  from clearskies.authentication import Authentication, Authorization, Public
10
7
  from clearskies.endpoint import Endpoint
11
8
  from clearskies.functional import routing
@@ -16,9 +13,9 @@ if TYPE_CHECKING:
16
13
 
17
14
 
18
15
  class EndpointGroup(
19
- clearskies.end.End, # type: ignore
20
- clearskies.configurable.Configurable,
21
- clearskies.di.InjectableProperties,
16
+ end.End, # type: ignore
17
+ configurable.Configurable,
18
+ di.InjectableProperties,
22
19
  ):
23
20
  """
24
21
  An endpoint group brings endpoints together: it basically handles routing.
@@ -216,32 +213,32 @@ class EndpointGroup(
216
213
  """
217
214
  The dependency injection container
218
215
  """
219
- di = clearskies.di.inject.Di()
216
+ di = di.inject.Di()
220
217
 
221
218
  """
222
219
  The base URL for the endpoint group.
223
220
 
224
221
  This URL is added as a prefix to all endpoints attached to the group. This includes any named URL parameters:
225
222
  """
226
- url = clearskies.configs.String(default="")
223
+ url = configs.String(default="")
227
224
 
228
225
  """
229
226
  The list of endpoints connected to this endpoint group
230
227
  """
231
- endpoints = clearskies.configs.EndpointList()
228
+ endpoints = configs.EndpointList()
232
229
 
233
- internal_casing = clearskies.configs.Select(["snake_case", "camelCase", "TitleCase"], default="snake_case")
234
- external_casing = clearskies.configs.Select(["snake_case", "camelCase", "TitleCase"], default="snake_case")
235
- response_headers = clearskies.configs.StringListOrCallable(default=[])
236
- authentication = clearskies.configs.Authentication(default=Public())
237
- authorization = clearskies.configs.Authorization(default=Authorization())
238
- security_headers = clearskies.configs.SecurityHeaders(default=[])
230
+ internal_casing = configs.Select(["snake_case", "camelCase", "TitleCase"], default="snake_case")
231
+ external_casing = configs.Select(["snake_case", "camelCase", "TitleCase"], default="snake_case")
232
+ response_headers = configs.StringListOrCallable(default=[])
233
+ authentication = configs.Authentication(default=Public())
234
+ authorization = configs.Authorization(default=Authorization())
235
+ security_headers = configs.SecurityHeaders(default=[])
239
236
 
240
237
  cors_header: SecurityHeader = None # type: ignore
241
238
  has_cors: bool = False
242
239
  endpoints_initialized = False
243
240
 
244
- @clearskies.decorators.parameters_to_properties
241
+ @decorators.parameters_to_properties
245
242
  def __init__(
246
243
  self,
247
244
  endpoints: list[Endpoint | Self],
@@ -325,7 +322,7 @@ class EndpointGroup(
325
322
  return self.respond_json(input_output, {"status": "client_error", "error": message}, status_code)
326
323
 
327
324
  def all_endpoints(self) -> list[Endpoint]:
328
- """Returns the full (recursive) list of all endpoints associated with this endpoint group"""
325
+ """Return the full (recursive) list of all endpoints associated with this endpoint group."""
329
326
  all_endpoints: list[Endpoint] = []
330
327
  for endpoint in self.endpoints:
331
328
  if hasattr(endpoint, "all_endpoints"):
@@ -1,19 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
- import inspect
4
- from collections import OrderedDict
5
- from typing import TYPE_CHECKING, Any, Type
3
+ from typing import TYPE_CHECKING, Any
6
4
 
7
- import clearskies.configs
8
- import clearskies.exceptions
9
- from clearskies import authentication, autodoc, typing
5
+ from clearskies import exceptions
10
6
  from clearskies.endpoints.simple_search import SimpleSearch
11
- from clearskies.functional import string
12
- from clearskies.input_outputs import InputOutput
13
7
 
14
8
  if TYPE_CHECKING:
15
- from clearskies import SecurityHeader
16
- from clearskies.model import Model
9
+ from clearskies import Model, autodoc
17
10
 
18
11
 
19
12
  class AdvancedSearch(SimpleSearch):
@@ -350,12 +343,12 @@ class AdvancedSearch(SimpleSearch):
350
343
  if pagination_data:
351
344
  error = self.model.validate_pagination_data(pagination_data, self.auto_case_internal_column_name)
352
345
  if error:
353
- raise clearskies.exceptions.ClientError(error)
346
+ raise exceptions.ClientError(error)
354
347
  if query_parameters:
355
- raise clearskies.exceptions.ClientError("Query parameters were found but are not supported.")
348
+ raise exceptions.ClientError("Query parameters were found but are not supported.")
356
349
  for key in request_data.keys():
357
350
  if key not in self.allowed_request_keys:
358
- raise clearskies.exceptions.ClientError(
351
+ raise exceptions.ClientError(
359
352
  f"Invalid request parameter found in request body: '{key}'. Expected parameters: "
360
353
  + ", ".join([self.auto_case_internal_column_name(key) for key in self.allowed_request_keys])
361
354
  )
@@ -363,7 +356,7 @@ class AdvancedSearch(SimpleSearch):
363
356
  sort_key_name = self.auto_case_internal_column_name("sort")
364
357
  sort = request_data.get(sort_key_name, [])
365
358
  if not isinstance(sort, list):
366
- raise clearskies.exceptions.ClientError(
359
+ raise exceptions.ClientError(
367
360
  f"'{sort_key_name}' property in request body should be a list, but I found a value of type "
368
361
  + sort.__class__.__name
369
362
  )
@@ -372,25 +365,25 @@ class AdvancedSearch(SimpleSearch):
372
365
  direction_key_name = self.auto_case_internal_column_name("direction")
373
366
  for index, sort_entry in enumerate(sort):
374
367
  if not isinstance(sort_entry, dict):
375
- raise clearskies.exceptions.ClientError(
368
+ raise exceptions.ClientError(
376
369
  f"'{sort_key_name}' should be a list of dictionaries, but entry #{index + 1} is a value of type '{sort_entry.__class__.__name}', not a dict"
377
370
  )
378
371
  for key_name in [column_key_name, direction_key_name]:
379
372
  if not sort_entry.get(key_name):
380
- raise clearskies.exceptions.ClientError(
373
+ raise exceptions.ClientError(
381
374
  f"Each entry in the sort list should contain both '{column_key_name}' and '{direction_key_name}' but entry #{index + 1} is missing '{key_name}'"
382
375
  )
383
376
  if not isinstance(sort_entry[key_name], str):
384
- raise clearskies.exceptions.ClientError(
377
+ raise exceptions.ClientError(
385
378
  f"{key_name}' must be a string, but for entry #{index + 1} it is a value of type "
386
379
  + sort_entry[key_name].__class__.__name__
387
380
  )
388
381
  if sort_entry[direction_key_name].lower() not in ["asc", "desc"]:
389
- raise clearskies.exceptions.ClientError(
382
+ raise exceptions.ClientError(
390
383
  f"{direction_key_name}' must be either 'ASC' or 'DESC', but a different value was found for entry #{index + 1}"
391
384
  )
392
385
  if self.auto_case_column_name(sort_entry[column_key_name], False) not in self.sortable_column_names:
393
- raise clearskies.exceptions.ClientError(
386
+ raise exceptions.ClientError(
394
387
  f"Invalid sort column for entry #{index + 1}. Allowed values are: "
395
388
  + ", ".join(
396
389
  [
@@ -402,7 +395,7 @@ class AdvancedSearch(SimpleSearch):
402
395
  where_key_name = self.auto_case_internal_column_name("where")
403
396
  where = request_data.get(where_key_name, [])
404
397
  if not isinstance(where, list):
405
- raise clearskies.exceptions.ClientError(
398
+ raise exceptions.ClientError(
406
399
  f"'{where_key_name}' property in request body should be a list, but I found a value of type "
407
400
  + where.__class__.__name
408
401
  )
@@ -412,21 +405,21 @@ class AdvancedSearch(SimpleSearch):
412
405
  value_key_name = self.auto_case_internal_column_name("value")
413
406
  for index, where_entry in enumerate(where):
414
407
  if not isinstance(where_entry, dict):
415
- raise clearskies.exceptions.ClientError(
408
+ raise exceptions.ClientError(
416
409
  f"'{where_key_name}' should be a list of dictionaries, but entry #{index + 1} is a value of type '{where_entry.__class__.__name}', not a dict"
417
410
  )
418
411
  for key_name in [column_key_name, operator_key_name, value_key_name]:
419
412
  if key_name not in where_entry:
420
- raise clearskies.exceptions.ClientError(
413
+ raise exceptions.ClientError(
421
414
  f"Each entry in the where list should contain '{column_key_name}', '{operator_key_name}', and '{value_key_name}', but entry #{index + 1} is missing '{key_name}'"
422
415
  )
423
416
  if key_name != value_key_name and not isinstance(where_entry[key_name], str):
424
- raise clearskies.exceptions.ClientError(
417
+ raise exceptions.ClientError(
425
418
  f"{key_name}' must be a string, but for entry #{index + 1} it is a value of type "
426
419
  + sort_entry[key_name].__class__.__name__
427
420
  )
428
421
  if where_entry[column_key_name] not in self.searchable_column_names:
429
- raise clearskies.exceptions.ClientError(
422
+ raise exceptions.ClientError(
430
423
  f"Invalid where column for entry #{index + 1}. Allowed values are: "
431
424
  + ", ".join(
432
425
  [
@@ -462,12 +455,12 @@ class AdvancedSearch(SimpleSearch):
462
455
  value if operator != "in" else value[0], where_entry[operator_key_name]
463
456
  )
464
457
  if error_allowed_operators:
465
- raise clearskies.exceptions.ClientError(
458
+ raise exceptions.ClientError(
466
459
  f"Invalid operator for entry #{index + 1}. Allowed operators are: "
467
460
  + ", ".join(column.allowed_search_operators(relationship_reference=column_name))
468
461
  )
469
462
  if error:
470
- raise clearskies.exceptions.ClientError(f"Invalid search value for entry #{index + 1}: {error}")
463
+ raise exceptions.ClientError(f"Invalid search value for entry #{index + 1}: {error}")
471
464
 
472
465
  def configure_model_from_request_data(
473
466
  self,
@@ -1,22 +1,16 @@
1
1
  from __future__ import annotations
2
2
 
3
- import inspect
4
- from collections import OrderedDict
5
3
  from typing import TYPE_CHECKING, Any
6
4
  from typing import Callable as CallableType
7
5
 
8
- import clearskies.configs
9
- import clearskies.exceptions
10
- from clearskies import authentication, autodoc, typing
6
+ from clearskies import authentication, autodoc, configs, decorators, exceptions
11
7
  from clearskies.endpoint import Endpoint
12
8
  from clearskies.functional import string
13
- from clearskies.input_outputs import InputOutput
14
9
 
15
10
  if TYPE_CHECKING:
16
- from clearskies import Column, SecurityHeader
11
+ from clearskies import Column, Model, Schema, SecurityHeader
17
12
  from clearskies.authentication import Authentication, Authorization
18
- from clearskies.model import Model
19
- from clearskies.schema import Schema
13
+ from clearskies.input_outputs import InputOutput
20
14
 
21
15
 
22
16
  class Callable(Endpoint):
@@ -114,7 +108,7 @@ class Callable(Endpoint):
114
108
  """
115
109
  The callable to execute when the endpoint is invoked
116
110
  """
117
- to_call = clearskies.configs.Callable(default=None)
111
+ to_call = configs.Callable(default=None)
118
112
 
119
113
  """
120
114
  A schema that describes the expected input from the client.
@@ -172,7 +166,7 @@ class Callable(Endpoint):
172
166
  ```
173
167
 
174
168
  """
175
- input_schema = clearskies.configs.Schema(default=None)
169
+ input_schema = configs.Schema(default=None)
176
170
 
177
171
  """
178
172
  Whether or not the return value is meant to be wrapped up in the standard clearskies response schema.
@@ -232,20 +226,20 @@ class Callable(Endpoint):
232
226
  Note that you can also return strings this way instead of objects/JSON.
233
227
 
234
228
  """
235
- return_standard_response = clearskies.configs.Boolean(default=True)
229
+ return_standard_response = configs.Boolean(default=True)
236
230
 
237
231
  """
238
232
  Set to true if the callable will be returning multiple records (used when building the auto-documentation)
239
233
  """
240
- return_records = clearskies.configs.Boolean(default=False)
234
+ return_records = configs.Boolean(default=False)
241
235
 
242
- @clearskies.decorators.parameters_to_properties
236
+ @decorators.parameters_to_properties
243
237
  def __init__(
244
238
  self,
245
239
  to_call: CallableType,
246
240
  url: str = "",
247
241
  request_methods: list[str] = ["GET"],
248
- model_class: type[clearskies.model.Model] | None = None,
242
+ model_class: type[Model] | None = None,
249
243
  readable_column_names: list[str] = [],
250
244
  writeable_column_names: list[str] = [],
251
245
  input_schema: type[Schema] | None = None,
@@ -281,7 +275,7 @@ class Callable(Endpoint):
281
275
  else:
282
276
  input_errors = self.find_input_errors_from_callable(input_output.request_data, input_output)
283
277
  if input_errors:
284
- raise clearskies.exceptions.InputErrors(input_errors)
278
+ raise exceptions.InputErrors(input_errors)
285
279
  response = self.di.call_function(self.to_call, **input_output.get_context_for_callables())
286
280
 
287
281
  if not self.return_standard_response: