clear-skies 1.18.6__py3-none-any.whl → 1.18.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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: clear-skies
3
- Version: 1.18.6
3
+ Version: 1.18.8
4
4
  Summary: A framework for building backends in the cloud
5
5
  Home-page: https://github.com/cmancone/clearskies
6
6
  License: MIT
@@ -61,7 +61,7 @@ clearskies/column_types/boolean.py,sha256=1yyM1CUfgD84pPE65c1OP1Qjf_J0Z45hjPrDR5
61
61
  clearskies/column_types/category_tree.py,sha256=PgNmzZPyqYS5NADH_QTCxLvDXZFxzv5ESKTkvPrrLXo,9140
62
62
  clearskies/column_types/column.py,sha256=TCGqtsCEYJr6BHs-PS1yFoUHGIwcx5oTujytNkTAcrk,13243
63
63
  clearskies/column_types/created.py,sha256=R8P3egUb7JEHoU_NtbmN9OIwBUHQ0XFi1GoAb5RpSkI,339
64
- clearskies/column_types/created_by_authorization_data.py,sha256=TBcALV3uchur2Aq9JCRCAihjZrZKc95VZJSIYgY3Ve0,557
64
+ clearskies/column_types/created_by_authorization_data.py,sha256=--1w1TOSo2CMwrpn6Y_iorl2RTqLgG8MbR8k27qreew,1108
65
65
  clearskies/column_types/created_by_ip.py,sha256=wwCUoEwHEVGN89x4xP7NJ6QR85Aum6v3JmxofoQrqtg,395
66
66
  clearskies/column_types/created_by_user_agent.py,sha256=sSYDRrqSjsCwcYlhF_s9NO-iDww3PaH6aO2ATp_SKGQ,419
67
67
  clearskies/column_types/datetime.py,sha256=MuVaeI6FMgevTdgv6djZjlHogs72TC1HTuYdw_6MF2M,3824
@@ -121,7 +121,7 @@ clearskies/functional/string.py,sha256=Jmz8G9PW41THIRfDT2WXtRk3yzJG3Zt4DKH6X2C2p
121
121
  clearskies/functional/validations.py,sha256=f1fTQ4rdFZouxoovAPg-YAgf0Q0QNpKEzxWWL7EFUHI,645
122
122
  clearskies/handlers/__init__.py,sha256=YIQeKkkFhXwn7Rcc7Qllh2RGkKB0nDjt1nwowqEuJ3E,968
123
123
  clearskies/handlers/advanced_search.py,sha256=aB6DaMIsms6luVPQLhPwV9sBTOyh4ip_gi-0l_52YLE,12934
124
- clearskies/handlers/base.py,sha256=K_6-HupAAjmVb9pusjit0aRM3VtF63YCLMT4a_ZKM2Q,22888
124
+ clearskies/handlers/base.py,sha256=6_glA2SDJHH03NnL1XLmlyMqRf_bWYVzCwrMy-vo5Qk,22890
125
125
  clearskies/handlers/callable.py,sha256=RxDPBuk1socPQvPhuKhCiidhAnXXLdSdG0sVtNAmi8c,8001
126
126
  clearskies/handlers/create.py,sha256=xj_hVYma2sKDK5Vq_R9wo8f0ZXfGlXTkYU71AMyKF2U,1232
127
127
  clearskies/handlers/crud_by_method.py,sha256=BOkPX-LUvQrbRLSbyTfRh4c8nPF51dZEXSKOl7m9ZYA,435
@@ -140,12 +140,12 @@ clearskies/handlers/list.py,sha256=f17wbS1gVopTTzV9rwmEb7KjHnoiwercENZbXJDwChI,2
140
140
  clearskies/handlers/mygrations.py,sha256=4iKpJKooqgNtAURwMl_FgsXUt8OYOaG_TY1OV1llQxY,2759
141
141
  clearskies/handlers/request_method_routing.py,sha256=DgPEz3tgbaUkXHsOriPbIctfSf4Gm4NxfRdVulH91Kg,1636
142
142
  clearskies/handlers/restful_api.py,sha256=1rJ2REX1sTAdbqaRuCclP375agrho4zNNQx6hXGa4nQ,9258
143
- clearskies/handlers/routing.py,sha256=tHJHNbszas2txUZiKl0xi2kB2x3Sjx6H8lLouoAWp4Y,3078
143
+ clearskies/handlers/routing.py,sha256=uWKWcEaiHVqfDebPkQVuG9AS8pOixW31wW0yIQ-25Aw,3079
144
144
  clearskies/handlers/schema_helper.py,sha256=62644USvFlZu_6tT7rb-k6t_5J3Q0uZsJwP5KREk_WM,4961
145
145
  clearskies/handlers/simple_routing.py,sha256=8T4eKLIurZO3ZdIPgi-0pypQp_X4BKkbet_Ymba5br4,9332
146
- clearskies/handlers/simple_routing_route.py,sha256=C-dDLubkUnAazxAcXoyGxMX4g24Iwz7wX5rf5T8T8kU,7376
146
+ clearskies/handlers/simple_routing_route.py,sha256=bdJh1Py6FhWU8kO_9au0rkjbRUXhafPInskg2tOfnVU,8609
147
147
  clearskies/handlers/simple_search.py,sha256=hZ0rMfhS-BB6LTpdl0I53pEUBgbgIwtXvcW_8ZEOZOs,6003
148
- clearskies/handlers/update.py,sha256=GAtkZpim6YI-XvtJ4qm-fwlz7gHYbUMCfvsfdD0_RSo,3799
148
+ clearskies/handlers/update.py,sha256=VCZkoID7i5VHq78fkIpjbRdXQ4Z3IuXEE5dfC8z4RZI,4088
149
149
  clearskies/handlers/write.py,sha256=VduGtjnFMQOvo3l0t-tUP4PExPJ9JEpuziHsquF08rE,9344
150
150
  clearskies/input_outputs/__init__.py,sha256=mQWL-u41FRTrPGuHe8FhLmcHjAEaUxjFwUf7RgDcbAs,182
151
151
  clearskies/input_outputs/cli.py,sha256=snIfLFakIqDXm-AXVN6qes0tZsg2IM7T1riFzDdHlMM,6254
@@ -163,7 +163,7 @@ clearskies/input_requirements/unique.py,sha256=gpbm9uoXcy8WCHsuWqAotwockbjDfJOWi
163
163
  clearskies/mocks/__init__.py,sha256=T68OUB9gGCX0WoisGzsY3Bt2cCFX7ILHKPqi6XKTJM0,113
164
164
  clearskies/mocks/input_output.py,sha256=2wD5GbUyVSkXcBg1GTZ-Oz9VzcYxNHfTlmZAODW-7CI,3898
165
165
  clearskies/mocks/models.py,sha256=DCzsnMddBvPoBA8JwwbSOhzY7enQWrosgeYD4gx2deI,5124
166
- clearskies/model.py,sha256=vUZ5lCDmZqvt11YC-ii15MdbftBM1Koz_JJ-KonjCdA,12691
166
+ clearskies/model.py,sha256=lXs5NcdR6gsK6ztH9W7Slulkw03a-3q5R8w3ht5ilYQ,12856
167
167
  clearskies/models.py,sha256=Oh1dDPg-IDIEt54oXVvKBBmksBYtStyw6Uu5fOfCs6E,12232
168
168
  clearskies/secrets/__init__.py,sha256=ctTmA_etV9G_5U21APWENI1HvThrBS4DidGWRtEDHQs,1053
169
169
  clearskies/secrets/additional_configs/__init__.py,sha256=cFCrbtKF5nuR061S2y1iKZp349x-y8Srdwe3VZbfSFU,1119
@@ -187,7 +187,7 @@ clearskies/tests/simple_api/models/__init__.py,sha256=nUA0W6fgXw_Bxa9CudkaDkC80t
187
187
  clearskies/tests/simple_api/models/status.py,sha256=PEhPbaQh5qdUNHp8O0gz91LOLENAEBtqSaHxUPXchaM,699
188
188
  clearskies/tests/simple_api/models/user.py,sha256=5_P4Tp1tTdX7PkMJ__epPM5MA7JAeVYGas69vcWloLc,819
189
189
  clearskies/tests/simple_api/users_api.py,sha256=KYXCgEofDxHeRdQK67txN5oYUPvxxmB8JTku7L-apk4,2344
190
- clear_skies-1.18.6.dist-info/LICENSE,sha256=3Ehd0g3YOpCj8sqj0Xjq5qbOtjjgk9qzhhD9YjRQgOA,1053
191
- clear_skies-1.18.6.dist-info/METADATA,sha256=IANeVgA5OpkPdBPEyGwYZ4guJj5xuhiaqp_-4HUBGb0,1366
192
- clear_skies-1.18.6.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
193
- clear_skies-1.18.6.dist-info/RECORD,,
190
+ clear_skies-1.18.8.dist-info/LICENSE,sha256=3Ehd0g3YOpCj8sqj0Xjq5qbOtjjgk9qzhhD9YjRQgOA,1053
191
+ clear_skies-1.18.8.dist-info/METADATA,sha256=C2EEmkDNduNK0DI7pww5bcCHX0oEzAkNBn4S3F3FsJQ,1366
192
+ clear_skies-1.18.8.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
193
+ clear_skies-1.18.8.dist-info/RECORD,,
@@ -18,4 +18,9 @@ class CreatedByAuthorizationData(String):
18
18
  return data
19
19
 
20
20
  authorization_data = self.di.build("input_output", cache=True).get_authorization_data()
21
- return {**data, self.name: authorization_data.get(self.config("authorization_data_key_name"), "N/A")}
21
+ # data comes last so that it can override the info in the authorization data. This seems counter-intuitive,
22
+ # but is important. You would think that you *don't* want the data from the authorization data to be
23
+ # overridden (since this is mainly used for logging), but the trouble is that there are a variety of use-cases
24
+ # where the application must provide the audit data. Examples include registration and login. In these
25
+ # cases, authorization data will be empty, and must be provided by the applicaiton.
26
+ return {self.name: authorization_data.get(self.config("authorization_data_key_name"), "N/A"), **data}
@@ -48,7 +48,7 @@ class Base(ABC):
48
48
  raise KeyError(f"Attempt to set unknown configuration setting '{key}' for handler '{class_name}'")
49
49
 
50
50
  self._check_configuration(configuration)
51
- self._configuration = self._finalize_configuration(self.apply_default_configuation(configuration))
51
+ self._configuration = self._finalize_configuration(self.apply_default_configuration(configuration))
52
52
 
53
53
  def _check_configuration(self, configuration):
54
54
  if not "authentication" in configuration:
@@ -113,7 +113,7 @@ class Base(ABC):
113
113
  f"Configuration error for handler '{self.__class__.__name__}': if provided, base_url must be a string"
114
114
  )
115
115
 
116
- def apply_default_configuation(self, configuration):
116
+ def apply_default_configuration(self, configuration):
117
117
  return {
118
118
  **self._global_configuration_defaults,
119
119
  **self._configuration_defaults,
@@ -56,7 +56,7 @@ class Routing(Base):
56
56
  raise KeyError(f"Attempt to set unknown configuration setting '{key}' for handler '{class_name}'")
57
57
 
58
58
  self._check_configuration(configuration)
59
- self._configuration = self._finalize_configuration(self.apply_default_configuation(configuration))
59
+ self._configuration = self._finalize_configuration(self.apply_default_configuration(configuration))
60
60
 
61
61
  def _check_configuration(self, configuration):
62
62
  super()._check_configuration(configuration)
@@ -1,9 +1,13 @@
1
+ import logging
1
2
  import urllib.parse
2
3
  import re
4
+ import json
3
5
  from ..autodoc.request import URLPath
4
6
  from ..autodoc.schema import String
5
7
  from . import simple_routing
6
8
 
9
+ logger = logging.getLogger(__name__)
10
+
7
11
 
8
12
  class SimpleRoutingRoute:
9
13
  _di = None
@@ -91,26 +95,45 @@ class SimpleRoutingRoute:
91
95
  to understand if there was no route match at all.
92
96
  """
93
97
  # if we're routing to a simple router then defer to it
98
+ incoming = f"Incoming request: [{request_method}] {full_path}. Check against route with url '{self._path}' "
99
+ if not self._methods:
100
+ incoming += " configured for any method except OPTIONS"
101
+ elif isinstance(self._methods, str):
102
+ incoming += f" with method '{self._methods}'"
103
+ else:
104
+ incoming += " with any of the following methods: " + ", ".join(self._methods)
94
105
  if self._routes_to_simple_routing:
95
106
  return self._handler.can_handle(full_path, request_method, is_cors=is_cors)
96
107
  # If we're routing for CORS then ignore the request method (since it won't match)
97
108
  if not is_cors and self._methods is not None and request_method not in self._methods:
109
+ logger.debug(
110
+ f"{incoming} Skipped because this route is not specifically configured for CORS, and this is an OPTIONS request."
111
+ )
98
112
  return None
99
113
  if self._resource_paths:
100
- return self._resource_path_match(full_path, self._path_parts, self._resource_paths)
114
+ results = self._resource_path_match(full_path, self._path_parts, self._resource_paths)
115
+ if not results:
116
+ logger.debug(f"{incoming} Not a match.")
117
+ else:
118
+ logger.debug(f"{incoming} Matched and extracted route data: " + json.dumps(results))
119
+ return results
101
120
  if self._path is not None:
102
121
  full_path = full_path.strip("/")
103
122
  my_path = self._path.strip("/")
104
123
  my_path_length = len(my_path)
105
124
  full_path_length = len(full_path)
106
125
  if my_path_length > full_path_length:
126
+ logger.debug(f"{incoming} Not a match. I'm too long to bother checking.")
107
127
  return None
108
128
  if full_path[:my_path_length] != my_path:
129
+ logger.debug(f"{incoming} Not a match. Our prefixes just don't match.")
109
130
  return None
110
131
  # make sure we don't get confused by partial matches. `user` should match `user/` and `user/5`,
111
132
  # but it shouldn't match `users/`
112
133
  if full_path_length > my_path_length and full_path[my_path_length] != "/":
134
+ logger.debug(f"{incoming} Not a match. I only partially matched the URL but not as a sub-directory.")
113
135
  return None
136
+ logger.debug(f"{incoming} Match!")
114
137
  return {}
115
138
 
116
139
  def _resource_path_match(self, requested_path, path_parts, resource_paths):
@@ -12,6 +12,7 @@ class Update(Write):
12
12
  "model": None,
13
13
  "model_class": None,
14
14
  "columns": None,
15
+ "column_overrides": None,
15
16
  "writeable_columns": None,
16
17
  "readable_columns": None,
17
18
  "where": [],
@@ -19,15 +20,19 @@ class Update(Write):
19
20
  "include_id_in_path": False,
20
21
  }
21
22
 
22
- def handle(self, input_output):
23
- input_data = self.request_data(input_output)
23
+ def get_model_id(self, input_output, input_data):
24
24
  routing_data = input_output.routing_data()
25
25
  if self.id_column_name in routing_data:
26
- model_id = routing_data[self.id_column_name]
27
- elif "id" in routing_data:
28
- model_id = routing_data["id"]
29
- else:
30
- raise ValueError("I didn't receive the ID in my routing data. I am probably misconfigured.")
26
+ return routing_data[self.id_column_name]
27
+ if "id" in routing_data:
28
+ return routing_data["id"]
29
+ raise ValueError("I didn't receive the ID in my routing data. I am probably misconfigured.")
30
+
31
+ def handle(self, input_output):
32
+ input_data = self.request_data(input_output)
33
+ model_id = self.get_model_id(input_output, input_data)
34
+ if not model_id:
35
+ return self.error(input_output, "Not Found", 404)
31
36
  id_column_name = self.id_column_name
32
37
  models = self._model.where(f"{id_column_name}={model_id}")
33
38
  for where in self.configuration("where"):
@@ -42,6 +47,7 @@ class Update(Write):
42
47
  input_output.routing_data(),
43
48
  input_output.get_authorization_data(),
44
49
  input_output,
50
+ overrides=self.configuration("column_overrides"),
45
51
  )
46
52
  authorization = self._configuration.get("authorization", None)
47
53
  if authorization and hasattr(authorization, "filter_models"):
@@ -58,7 +64,7 @@ class Update(Write):
58
64
  }
59
65
  if input_errors:
60
66
  raise InputError(input_errors)
61
- model.save(input_data, columns=self._columns)
67
+ model.save(input_data, columns=self._get_writeable_columns())
62
68
 
63
69
  return self.success(input_output, self._model_as_json(model, input_output))
64
70
 
clearskies/model.py CHANGED
@@ -130,24 +130,26 @@ class Model(Models):
130
130
  """
131
131
  if not len(data):
132
132
  raise ValueError("You have to pass in something to save!")
133
- if columns is None:
134
- columns = self.columns()
133
+ save_columns = self.columns()
134
+ if columns is not None:
135
+ for column in columns.values():
136
+ save_columns[column.name] = column
135
137
 
136
138
  old_data = self.data
137
- data = self.columns_pre_save(data, columns)
139
+ data = self.columns_pre_save(data, save_columns)
138
140
  data = self.pre_save(data)
139
141
  if data is None:
140
142
  raise ValueError("pre_save forgot to return the data array!")
141
143
 
142
- to_save = self.columns_to_backend(data, columns)
143
- to_save = self.to_backend(to_save, columns)
144
+ to_save = self.columns_to_backend(data, save_columns)
145
+ to_save = self.to_backend(to_save, save_columns)
144
146
  if self.exists:
145
147
  new_data = self._backend.update(self._data[self.id_column_name], to_save, self)
146
148
  else:
147
149
  new_data = self._backend.create(to_save, self)
148
- id = self._backend.column_from_backend(columns[self.id_column_name], new_data[self.id_column_name])
150
+ id = self._backend.column_from_backend(save_columns[self.id_column_name], new_data[self.id_column_name])
149
151
 
150
- data = self.columns_post_save(data, id, columns)
152
+ data = self.columns_post_save(data, id, save_columns)
151
153
  self.post_save(data, id)
152
154
 
153
155
  self.data = new_data
@@ -155,7 +157,7 @@ class Model(Models):
155
157
  self._previous_data = old_data
156
158
  self._touched_columns = list(data.keys())
157
159
 
158
- self.columns_save_finished(columns)
160
+ self.columns_save_finished(save_columns)
159
161
  self.save_finished()
160
162
 
161
163
  return True
@@ -332,10 +334,10 @@ class Model(Models):
332
334
  """
333
335
  pass
334
336
 
335
- def where_for_request(self, models, routing_data, authorization_data, input_output):
337
+ def where_for_request(self, models, routing_data, authorization_data, input_output, overrides=None):
336
338
  """
337
339
  A hook to automatically apply filtering whenever the model makes an appearance in a get/update/list/search handler.
338
340
  """
339
- for column in self.columns().values():
341
+ for column in self.columns(overrides=overrides).values():
340
342
  models = column.where_for_request(models, routing_data, authorization_data, input_output)
341
343
  return models