clear-skies 1.22.1__py3-none-any.whl → 1.22.3__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.22.1
3
+ Version: 1.22.3
4
4
  Summary: A framework for building backends in the cloud
5
5
  Home-page: https://github.com/cmancone/clearskies
6
6
  License: MIT
@@ -47,6 +47,7 @@ clearskies/autodoc/schema/password.py,sha256=Ptj8OeddAL4h69KWqZ6ubZ2awR13xdDIrNe
47
47
  clearskies/autodoc/schema/string.py,sha256=oxZPCxYYhWnNHdbtwD3QuniStbj8XbBBpDTFXgPR1VU,244
48
48
  clearskies/backends/__init__.py,sha256=IqIpTNdA0U14wFNBkotwc9kZr_mMw3ry8LnldOZ0HLs,755
49
49
  clearskies/backends/api_backend.py,sha256=PQyT00pMtZZUQhfWySzVbXZ2GpycO93CRKVOmUFeo10,15073
50
+ clearskies/backends/api_get_only_backend.py,sha256=OWXdoaSaF5fWb7GN1OgkGED3edKfoeOBMHc8bKtm2ig,3885
50
51
  clearskies/backends/backend.py,sha256=fkL-De0MUdzcS2JG_spSUQZIVL9oRFvaL6SP26JPpcI,7399
51
52
  clearskies/backends/cursor_backend.py,sha256=VntlPS6z6bnZOC3XRJ-WFf5gK3pFUhH_qJpnZn8hl9U,11278
52
53
  clearskies/backends/example_backend.py,sha256=jVpv0LZpNUEJGko0XqioLkHmZHbCW6M4YyNvzKlZcDw,1413
@@ -58,7 +59,7 @@ clearskies/backends/secrets_backend.py,sha256=4lzrgdL_O_pgCT5HknV2gotFgp9GzjQ5_2
58
59
  clearskies/binding_config.py,sha256=bF8LBNEgJacwKCqToAtDqN9hv5omzU7zt_4qB9KPtE0,457
59
60
  clearskies/column_types/__init__.py,sha256=wofhLfyW00I6tb6o9DMsMx7j9hlbbqefhDzWfw0Row0,4731
60
61
  clearskies/column_types/audit.py,sha256=2YcrZVVElpOUdmxYTQ_6CshL1HVou6fz65dOOs_b8Gw,9659
61
- clearskies/column_types/belongs_to.py,sha256=tH1tbTOfjifSNuVjO-KbMF7GiUIoLfcDItrrS3TGGM8,11044
62
+ clearskies/column_types/belongs_to.py,sha256=E7Wi84vr2PnNw7TBCoZa8jkpwiJhO-iQmZ_ekq26kTs,12206
62
63
  clearskies/column_types/boolean.py,sha256=1yyM1CUfgD84pPE65c1OP1Qjf_J0Z45hjPrDR51AUkQ,1878
63
64
  clearskies/column_types/category_tree.py,sha256=kPx0fNTJxHaaEI_-0JxQ7NBcV2bYgUDGmtf1wmTqoEg,13172
64
65
  clearskies/column_types/column.py,sha256=ftuDFswjk-KE9Frxo1rhgkjr4sjSjnUc5ZtfNrnGLIc,15530
@@ -73,7 +74,7 @@ clearskies/column_types/datetime.py,sha256=xtuZpUC9fA16i1oO80kPIx--8RDPuei9RdsDD
73
74
  clearskies/column_types/datetime_micro.py,sha256=ewQSniCc2MmNIyX2XNuNcCIwh5Fpf1HcvpLfzB8lz8g,382
74
75
  clearskies/column_types/email.py,sha256=qq0Yo_C3KxUqT68q2HWXocBBR4xwMqjxcIdgZRv218U,584
75
76
  clearskies/column_types/float.py,sha256=j8jJeBueSOusPtAFCWgLHYBncfLnqT1U7bh1zcAkYiA,1332
76
- clearskies/column_types/has_many.py,sha256=Z4oM1g2dQx6H9171c52FLC41nLryCOKmh51I75mYmmY,5895
77
+ clearskies/column_types/has_many.py,sha256=z1tco_KWLr8WqKk5X8HJwdfjxpT2WwWNb5hgwDHM2fU,7432
77
78
  clearskies/column_types/has_one.py,sha256=uphIPUuHLwwmhljLMaKKPujR6TYTT7onn-hHUF6S_IY,2230
78
79
  clearskies/column_types/integer.py,sha256=dGIluusPmhLRNg7PplOJLbQI2AXojqRBUHt8ekYWNVI,1386
79
80
  clearskies/column_types/json.py,sha256=TbZkdwCoZYhbALUxof2jENGfaq2i5TlcyBcmo7XzDGQ,652
@@ -206,7 +207,7 @@ clearskies/tests/simple_api/models/__init__.py,sha256=nUA0W6fgXw_Bxa9CudkaDkC80t
206
207
  clearskies/tests/simple_api/models/status.py,sha256=PEhPbaQh5qdUNHp8O0gz91LOLENAEBtqSaHxUPXchaM,699
207
208
  clearskies/tests/simple_api/models/user.py,sha256=5_P4Tp1tTdX7PkMJ__epPM5MA7JAeVYGas69vcWloLc,819
208
209
  clearskies/tests/simple_api/users_api.py,sha256=KYXCgEofDxHeRdQK67txN5oYUPvxxmB8JTku7L-apk4,2344
209
- clear_skies-1.22.1.dist-info/LICENSE,sha256=3Ehd0g3YOpCj8sqj0Xjq5qbOtjjgk9qzhhD9YjRQgOA,1053
210
- clear_skies-1.22.1.dist-info/METADATA,sha256=ZETHX5y_yVlBABtBlc3EwHmM-xoE-KikkR_Jca2sFMk,1817
211
- clear_skies-1.22.1.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
212
- clear_skies-1.22.1.dist-info/RECORD,,
210
+ clear_skies-1.22.3.dist-info/LICENSE,sha256=3Ehd0g3YOpCj8sqj0Xjq5qbOtjjgk9qzhhD9YjRQgOA,1053
211
+ clear_skies-1.22.3.dist-info/METADATA,sha256=YtXu3sQzPtg-LoA92QMTlV0R6cQEuzkO_e5AXs_1JVw,1817
212
+ clear_skies-1.22.3.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
213
+ clear_skies-1.22.3.dist-info/RECORD,,
@@ -0,0 +1,101 @@
1
+ from .api_backend import ApiBackend
2
+ from typing import Any, Callable, Dict, List, Tuple
3
+ from .. import model
4
+
5
+
6
+ class ApiGetOnlyBackend(ApiBackend):
7
+ _requests = None
8
+ _auth = None
9
+ _id_column_name = None
10
+
11
+ _allowed_configs = [
12
+ "wheres",
13
+ "table_name",
14
+ "model_columns",
15
+ "select_all",
16
+ ]
17
+
18
+ def __init__(self, requests):
19
+ self._requests = requests
20
+
21
+ def configure(self, auth=None, origin=None, id_column_name="id"):
22
+ self._auth = auth
23
+ self._origin = origin
24
+ self._id_column_name = id_column_name
25
+
26
+ def records_url(self, configuration):
27
+ record_id = None
28
+ for where in configuration["wheres"]:
29
+ if where["column"] == "id":
30
+ record_id = where["values"][0]
31
+ if record_id:
32
+ return self.auth_origin + configuration["table_name"].strip("/") + f"/{record_id}"
33
+ return self.auth_origin + configuration["table_name"].strip("/") + "/search"
34
+
35
+ def delete_url(self, id: str, model: model.Model) -> str:
36
+ table_name = model.table_name().rstrip("/")
37
+ return f"{table_name}/{id}"
38
+
39
+ def update_url(self, id: str, model: model.Model) -> str:
40
+ table_name = model.table_name().rstrip("/")
41
+ return f"{table_name}/{id}"
42
+
43
+ def create_url(self, data: Dict[str, Any], model: model.Model) -> str:
44
+ return model.table_name().rstrip("/")
45
+
46
+ def records_method(self, configuration: Dict[str, Any]) -> str:
47
+ return "POST"
48
+
49
+ def count_method(self, configuration: Dict[str, Any]) -> str:
50
+ return "POST"
51
+
52
+ def _build_delete_request(self, id, model):
53
+ data = model.data
54
+ (url, data) = self._finalize_url_and_data(self.delete_url(id, model), data)
55
+ return [url, self.delete_method(id, model), {}, {}]
56
+
57
+ def _build_count_request(self, configuration):
58
+ [url, method, json_data, headers] = super()._build_count_request(configuration)
59
+ json_data["count_only"] = True
60
+ return [url, method, json_data, headers]
61
+
62
+ def records(self, configuration, model, next_page_data={}):
63
+ configuration = self._check_query_configuration(configuration)
64
+ [url, method, json_data, headers] = self._build_records_request(configuration)
65
+ response = self._execute_request(url, method, json=json_data, headers=headers).json()
66
+ records = self._map_records_response(response)
67
+ for next_page_key in ["nextPage", "NextPage", "next_page"]:
68
+ if response.get("pagination", {}).get(next_page_key):
69
+ for key, value in response["pagination"][next_page_key].items():
70
+ next_page_data[key] = value
71
+ return records
72
+
73
+ def _as_post_data(self, configuration):
74
+ data = {
75
+ "where": list(
76
+ map(lambda where: self._where_for_post(where, configuration["model_columns"]), configuration["wheres"])
77
+ ),
78
+ "sort": configuration["sorts"],
79
+ "start": configuration["pagination"].get("start", 0),
80
+ "limit": configuration["limit"],
81
+ }
82
+ return {key: value for (key, value) in data.items() if value}
83
+
84
+ def _where_for_post(self, where, columns):
85
+ prefix = ""
86
+ if where.get("table"):
87
+ prefix = where["table"] + "."
88
+ return {
89
+ "column": prefix + where["column"],
90
+ "operator": where["operator"],
91
+ "value": self.normalize_outgoing_value(where, columns, where["values"][0]),
92
+ }
93
+
94
+ def normalize_outgoing_value(self, where, columns, value):
95
+ column_name = where["column"]
96
+ if where.get("table") or column_name not in columns:
97
+ return value
98
+ normalized_data = self.column_to_backend(columns[column_name], {column_name: value})
99
+ if column_name in normalized_data:
100
+ return normalized_data[column_name]
101
+ return value
@@ -34,6 +34,7 @@ class BelongsTo(String):
34
34
  "model_column_name",
35
35
  "readable_parent_columns",
36
36
  "join_type",
37
+ "where",
37
38
  ]
38
39
 
39
40
  def __init__(self, di):
@@ -43,6 +44,7 @@ class BelongsTo(String):
43
44
  super()._check_configuration(configuration)
44
45
  self.validate_models_class(configuration["parent_models_class"])
45
46
 
47
+ error_prefix = f"Configuration error for '{self.name}' in '{self.model_class.__name__}':"
46
48
  if not configuration.get("model_column_name") and self.name[-3:] != "_id":
47
49
  raise ValueError(
48
50
  f"Invalid name for column '{self.name}' in '{self.model_class.__name__}' - "
@@ -50,19 +52,14 @@ class BelongsTo(String):
50
52
  + "that the parent model can be fetched from."
51
53
  )
52
54
  if configuration.get("model_column_name") and type(configuration.get("model_column_name")) != str:
53
- raise ValueError(
54
- f"Configuration error for '{self.name}' in '{self.model_class.__name__}': 'model_column_name' must be a string."
55
- )
55
+ raise ValueError(f"{error_prefix} 'model_column_name' must be a string.")
56
56
 
57
57
  join_type = configuration.get("join_type")
58
58
  if join_type and join_type.upper() not in ["LEFT", "INNER"]:
59
- raise ValueError(
60
- f"Configuration error for '{self.name}' in '{self.model_class.__name__}': join_type must be INNER or LEFT"
61
- )
59
+ raise ValueError(f"{error_prefix} join_type must be INNER or LEFT")
62
60
 
63
61
  if configuration.get("readable_parent_columns"):
64
62
  parent_columns = self.di.build(configuration["parent_models_class"], cache=True).raw_columns_configuration()
65
- error_prefix = f"Configuration error for '{self.name}' in '{self.model_class.__name__}':"
66
63
  readable_parent_columns = configuration["readable_parent_columns"]
67
64
  if not hasattr(readable_parent_columns, "__iter__"):
68
65
  raise ValueError(
@@ -81,6 +78,19 @@ class BelongsTo(String):
81
78
  + "column does not exist in the model class."
82
79
  )
83
80
 
81
+ wheres = configuration.get("where")
82
+ if wheres:
83
+ if not isinstance(wheres, list):
84
+ raise ValueError(
85
+ f"{error_prefix} 'where' must be a list of where conditions or callables that return where conditions"
86
+ )
87
+ for index, where in enumerate(wheres):
88
+ if callable(where) or isinstance(where, str):
89
+ continue
90
+ raise ValueError(
91
+ f"{error_prefix} 'where' must be a list of where conditions or callables that return where conditions, but the item in entry #${index+1} was neither a string nor a callable"
92
+ )
93
+
84
94
  def _finalize_configuration(self, configuration):
85
95
  return {
86
96
  **super()._finalize_configuration(configuration),
@@ -89,6 +99,7 @@ class BelongsTo(String):
89
99
  if configuration.get("model_column_name")
90
100
  else self.name[:-3],
91
101
  "join_type": configuration.get("join_type", "INNER").upper(),
102
+ "where": configuration.get("where", []),
92
103
  },
93
104
  }
94
105
 
@@ -149,7 +160,17 @@ class BelongsTo(String):
149
160
 
150
161
  @property
151
162
  def parent_models(self):
152
- return self.di.build(self.config("parent_models_class"), cache=True)
163
+ parents = self.di.build(self.config("parent_models_class"), cache=True)
164
+ for where in self.config("where"):
165
+ if callable(where):
166
+ parents = self.di.call_function(where, model=parents)
167
+ if not parents:
168
+ raise ValueError(
169
+ f"Configuration error for column '{self.name}' in model '{self.model_class.__name__}': when 'where' is a callable, it must return a models class, but when the callable in where entry #{index+1} was called, it did not return the models class"
170
+ )
171
+ else:
172
+ parents = parents.where(where)
173
+ return parents
153
174
 
154
175
  @property
155
176
  def parent_columns(self):
@@ -27,6 +27,7 @@ class HasMany(Column):
27
27
  "is_readable",
28
28
  "readable_child_columns",
29
29
  "parent_id_column_name",
30
+ "where",
30
31
  ]
31
32
 
32
33
  def __init__(self, di):
@@ -63,9 +64,10 @@ class HasMany(Column):
63
64
 
64
65
  def _check_configuration(self, configuration):
65
66
  super()._check_configuration(configuration)
67
+ error_prefix = f"Configuration error for '{self.name}' in '{self.model_class.__name__}':"
68
+
66
69
  if configuration.get("is_readable"):
67
70
  child_columns = self.di.build(configuration["child_models_class"], cache=True).raw_columns_configuration()
68
- error_prefix = f"Configuration error for '{self.name}' in '{self.model_class.__name__}':"
69
71
  if not "readable_child_columns" in configuration:
70
72
  raise ValueError(f"{error_prefix} must provide 'readable_child_columns' if is_readable is set")
71
73
  readable_child_columns = configuration["readable_child_columns"]
@@ -86,6 +88,27 @@ class HasMany(Column):
86
88
  + "column does not exist in the model class."
87
89
  )
88
90
 
91
+ wheres = configuration.get("where")
92
+ if wheres:
93
+ if not isinstance(wheres, list):
94
+ raise ValueError(
95
+ f"{error_prefix} 'where' must be a list of where conditions or callables that return where conditions"
96
+ )
97
+ for index, where in enumerate(wheres):
98
+ if callable(where) or isinstance(where, str):
99
+ continue
100
+ raise ValueError(
101
+ f"{error_prefix} 'where' must be a list of where conditions or callables that return where conditions, but the item in entry #${index+1} was neither a string nor a callable"
102
+ )
103
+
104
+ def _finalize_configuration(self, configuration):
105
+ return {
106
+ **super()._finalize_configuration(configuration),
107
+ **{
108
+ "where": configuration.get("where", []),
109
+ },
110
+ }
111
+
89
112
  def get_child_columns(self):
90
113
  if "child_columns" not in self.configuration:
91
114
  self.configuration["child_columns"] = self.child_models.columns()
@@ -119,7 +142,17 @@ class HasMany(Column):
119
142
 
120
143
  @property
121
144
  def child_models(self):
122
- return self.di.build(self.config("child_models_class"), cache=True)
145
+ children = self.di.build(self.config("child_models_class"), cache=True)
146
+ for index, where in enumerate(self.config("where")):
147
+ if callable(where):
148
+ children = self.di.call_function(where, model=children)
149
+ if not children:
150
+ raise ValueError(
151
+ f"Configuration error for column '{self.name}' in model '{self.model_class.__name__}': when 'where' is a callable, it must return a models class, but when the callable in where entry #{index+1} was called, it did not return the models class"
152
+ )
153
+ else:
154
+ children = children.where(where)
155
+ return children
123
156
 
124
157
  def documentation(self, name=None, example=None, value=None):
125
158
  columns = self.get_child_columns()