clear-skies 1.20.3__py3-none-any.whl → 1.21.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

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.20.3
3
+ Version: 1.21.1
4
4
  Summary: A framework for building backends in the cloud
5
5
  Home-page: https://github.com/cmancone/clearskies
6
6
  License: MIT
@@ -25,6 +25,7 @@ Requires-Dist: jose (>=1.0.0,<2.0.0) ; extra == "jose"
25
25
  Requires-Dist: jwcrypto (>=1.5.6,<2.0.0) ; extra == "jwcrypto"
26
26
  Requires-Dist: pymysql (>=1.1.0,<2.0.0) ; extra == "mysql"
27
27
  Requires-Dist: requests (>=2.31.0,<3.0.0)
28
+ Requires-Dist: typing-extensions (>=4.12.0,<5.0.0) ; python_version >= "3.10" and python_version < "3.11"
28
29
  Project-URL: Repository, https://github.com/cmancone/clearskies
29
30
  Description-Content-Type: text/markdown
30
31
 
@@ -61,15 +61,15 @@ clearskies/column_types/belongs_to.py,sha256=tH1tbTOfjifSNuVjO-KbMF7GiUIoLfcDItr
61
61
  clearskies/column_types/boolean.py,sha256=1yyM1CUfgD84pPE65c1OP1Qjf_J0Z45hjPrDR51AUkQ,1878
62
62
  clearskies/column_types/category_tree.py,sha256=PgNmzZPyqYS5NADH_QTCxLvDXZFxzv5ESKTkvPrrLXo,9140
63
63
  clearskies/column_types/column.py,sha256=ftuDFswjk-KE9Frxo1rhgkjr4sjSjnUc5ZtfNrnGLIc,15530
64
- clearskies/column_types/created.py,sha256=S78e1VuvvUPYiI79gWGqlPzMSLIbXMpaYQMw8as7l38,615
64
+ clearskies/column_types/created.py,sha256=LIWSzPJ9rbHuk1u53pNvKtVCOG9y9XCn-mEEsSi97Zc,649
65
65
  clearskies/column_types/created_by_authorization_data.py,sha256=--1w1TOSo2CMwrpn6Y_iorl2RTqLgG8MbR8k27qreew,1108
66
66
  clearskies/column_types/created_by_header.py,sha256=5sY6vjtra-BrAgUE4zkuLhBtzCVWHNtJPwMozPw_7_s,520
67
67
  clearskies/column_types/created_by_ip.py,sha256=wwCUoEwHEVGN89x4xP7NJ6QR85Aum6v3JmxofoQrqtg,395
68
68
  clearskies/column_types/created_by_routing_data.py,sha256=EhVorRaGV2OhEb0YSPwPmrsK2NQycYgGEd4ab8-qI2I,569
69
69
  clearskies/column_types/created_by_user_agent.py,sha256=sSYDRrqSjsCwcYlhF_s9NO-iDww3PaH6aO2ATp_SKGQ,419
70
- clearskies/column_types/created_micro.py,sha256=EqeHB3ZHbi4nCa1rbrKy1TbooT9U3Kfw5z4jUvLi32U,636
71
- clearskies/column_types/datetime.py,sha256=OEy5eMfqWp6KOqEHQa3Ga_tl0oawtcMRBafI1SPYDFk,4275
72
- clearskies/column_types/datetime_micro.py,sha256=3DxtkeJZhWigdni7QEJFV3BJuCmXJMyFQjDZ9jyI144,328
70
+ clearskies/column_types/created_micro.py,sha256=Y_YADPg-TYPfWCFuGbs1b7NeM1Ixi_oBP8OQfJgTI7s,670
71
+ clearskies/column_types/datetime.py,sha256=VCVLDTxEN1yQ0h-BprejKWCYSOneQkvm7EeKy3c_T-Q,4339
72
+ clearskies/column_types/datetime_micro.py,sha256=ewQSniCc2MmNIyX2XNuNcCIwh5Fpf1HcvpLfzB8lz8g,382
73
73
  clearskies/column_types/email.py,sha256=qq0Yo_C3KxUqT68q2HWXocBBR4xwMqjxcIdgZRv218U,584
74
74
  clearskies/column_types/float.py,sha256=j8jJeBueSOusPtAFCWgLHYBncfLnqT1U7bh1zcAkYiA,1332
75
75
  clearskies/column_types/has_many.py,sha256=Z4oM1g2dQx6H9171c52FLC41nLryCOKmh51I75mYmmY,5895
@@ -81,9 +81,9 @@ clearskies/column_types/many_to_many_with_data.py,sha256=NOYjZedeLIWVyDV4BTRyNmh
81
81
  clearskies/column_types/phone.py,sha256=aUKshuknqcklA0LhUAdIgCslmAXXnWtXln1q5js8Eh8,1611
82
82
  clearskies/column_types/select.py,sha256=1oBslTJ_7QCjlFeEcwJVRL-ED4sXwCESVFRAOonvG2I,297
83
83
  clearskies/column_types/string.py,sha256=XbHC31TmlW0k86cvdVJBDyowU8Xis6Te6R0rPLXgLpI,863
84
- clearskies/column_types/timestamp.py,sha256=XQ8SHKrvPbgaXWlGP_1dKn9sYOFbcGVuSmGjWnTVTl8,2820
85
- clearskies/column_types/updated.py,sha256=FNyRK-PS6lgFQ9QN2VrcIZP2g5EHGhll6pf-MgqMH7s,566
86
- clearskies/column_types/updated_micro.py,sha256=3geqsV4nsPB9xDKyvQDGniU-KMqM8WvoqHfUYcBqGJY,587
84
+ clearskies/column_types/timestamp.py,sha256=q_9-zXm8AqJVFpSAvl0O2yqfxPFy4id4TxEh5qRQVmE,2866
85
+ clearskies/column_types/updated.py,sha256=ZSaM97bjFK-vMkPBMo8WnVOgzS77K5mR6KAArC1CbhE,634
86
+ clearskies/column_types/updated_micro.py,sha256=Z39_D574V6rY33UhY8Yruv-KeWw5PJw5yeuUQJfjE4A,655
87
87
  clearskies/column_types/uuid.py,sha256=pA6Cd-1QSRuUpz0PxWAGRdG01hW7bpIicOhhJvAvDkQ,711
88
88
  clearskies/columns.py,sha256=3PgLopL1l20vVWaUqOJkwXMr06a-gFHEsbExfOWVB4I,5925
89
89
  clearskies/condition_parser.py,sha256=IXl51-rDxws7-nAtTVu_m6gTegtpkDnoGAFX2MTPDIo,6589
@@ -120,7 +120,7 @@ clearskies/di/__init__.py,sha256=T7SgQNny2XAZQPeFkdmp1XxxmEVxtnpcRiGK8YflkwU,304
120
120
  clearskies/di/additional_config.py,sha256=jdoS_HWC0MAabori3WwLRAG1i5YKZmQfQ1o0hCoxsPs,526
121
121
  clearskies/di/additional_config_auto_import.py,sha256=m57IODPbnCAus9iDu3mDp42u4H87oPZvjAlBGoS8uRQ,111
122
122
  clearskies/di/di.py,sha256=g0U0PI73eNp0mkGH3KUN1fmqNic5eEUK-_IB8hQh-Kg,15511
123
- clearskies/di/standard_dependencies.py,sha256=vO2fbVQkuH7R-YqWu1eM2xcuR2SreFwcSeLmEVz2e8w,4396
123
+ clearskies/di/standard_dependencies.py,sha256=fgD4GT4Z0bcGPBaPDrzCqCtblD0UP572XD5d0O_DJxE,4523
124
124
  clearskies/di/test_module/__init__.py,sha256=7YHQF7JHP0FdI7GdEGANSZ_t1EISQYhUNm1wqOg0NKw,88
125
125
  clearskies/di/test_module/another_module/__init__.py,sha256=8SRmHPDepLKGWTUSc1ucDF6U8mJPsNDsBDmBQCpzPWo,35
126
126
  clearskies/di/test_module/module_class.py,sha256=I_-wnMuHfbsvti-7d2Z4bXnr6deo__uvww9nds9qrlE,46
@@ -181,8 +181,8 @@ clearskies/input_requirements/unique.py,sha256=gpbm9uoXcy8WCHsuWqAotwockbjDfJOWi
181
181
  clearskies/mocks/__init__.py,sha256=T68OUB9gGCX0WoisGzsY3Bt2cCFX7ILHKPqi6XKTJM0,113
182
182
  clearskies/mocks/input_output.py,sha256=2wD5GbUyVSkXcBg1GTZ-Oz9VzcYxNHfTlmZAODW-7CI,3898
183
183
  clearskies/mocks/models.py,sha256=DCzsnMddBvPoBA8JwwbSOhzY7enQWrosgeYD4gx2deI,5124
184
- clearskies/model.py,sha256=6H6eGQqMBz22bWu20N4xy7XPa1qYgfvNWik5cXH2DZg,13137
185
- clearskies/models.py,sha256=WJUUPSJ1rhDvAv1DpNDw0igNmhpn7PlZGh0f_g9sl5E,12766
184
+ clearskies/model.py,sha256=ZI75_1KP4J-PID2Aw9l_mNHCeY-HOZH887QKdxRoTdg,13511
185
+ clearskies/models.py,sha256=1H5Vohv1U4avN5_YHqULzc7ynZDRytmZ56x775xWTIo,13038
186
186
  clearskies/secrets/__init__.py,sha256=ctTmA_etV9G_5U21APWENI1HvThrBS4DidGWRtEDHQs,1053
187
187
  clearskies/secrets/additional_configs/__init__.py,sha256=cFCrbtKF5nuR061S2y1iKZp349x-y8Srdwe3VZbfSFU,1119
188
188
  clearskies/secrets/additional_configs/mysql_connection_dynamic_producer.py,sha256=fOt2eOrVtQXhnK05XuSfWw9GoX6klxXLisJcFT0ycAE,2562
@@ -205,7 +205,7 @@ clearskies/tests/simple_api/models/__init__.py,sha256=nUA0W6fgXw_Bxa9CudkaDkC80t
205
205
  clearskies/tests/simple_api/models/status.py,sha256=PEhPbaQh5qdUNHp8O0gz91LOLENAEBtqSaHxUPXchaM,699
206
206
  clearskies/tests/simple_api/models/user.py,sha256=5_P4Tp1tTdX7PkMJ__epPM5MA7JAeVYGas69vcWloLc,819
207
207
  clearskies/tests/simple_api/users_api.py,sha256=KYXCgEofDxHeRdQK67txN5oYUPvxxmB8JTku7L-apk4,2344
208
- clear_skies-1.20.3.dist-info/LICENSE,sha256=3Ehd0g3YOpCj8sqj0Xjq5qbOtjjgk9qzhhD9YjRQgOA,1053
209
- clear_skies-1.20.3.dist-info/METADATA,sha256=zXR2NXaY80ulWCP4Jqavr4nu1NL5PkyIl11qvSOdPT0,1711
210
- clear_skies-1.20.3.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
211
- clear_skies-1.20.3.dist-info/RECORD,,
208
+ clear_skies-1.21.1.dist-info/LICENSE,sha256=3Ehd0g3YOpCj8sqj0Xjq5qbOtjjgk9qzhhD9YjRQgOA,1053
209
+ clear_skies-1.21.1.dist-info/METADATA,sha256=gUmBHTE31EscgWpY1XRDst7EdoQlbySciQkEvh_fdoA,1817
210
+ clear_skies-1.21.1.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
211
+ clear_skies-1.21.1.dist-info/RECORD,,
@@ -8,8 +8,8 @@ class Created(DateTime):
8
8
  "utc",
9
9
  ]
10
10
 
11
- def __init__(self, di, datetime):
12
- super().__init__(di)
11
+ def __init__(self, di, datetime, timezone):
12
+ super().__init__(di, timezone)
13
13
  self.datetime = datetime
14
14
 
15
15
  @property
@@ -22,5 +22,5 @@ class Created(DateTime):
22
22
  if self.config("utc", silent=True):
23
23
  now = self.datetime.datetime.now(self.datetime.timezone.utc)
24
24
  else:
25
- now = self.datetime.datetime.now()
25
+ now = self.datetime.datetime.now(self._timezone)
26
26
  return {**data, self.name: now}
@@ -8,8 +8,8 @@ class CreatedMicro(DateTimeMicro):
8
8
  "utc",
9
9
  ]
10
10
 
11
- def __init__(self, di, datetime):
12
- super().__init__(di)
11
+ def __init__(self, di, datetime, timezone):
12
+ super().__init__(di, timezone)
13
13
  self.datetime = datetime
14
14
 
15
15
  @property
@@ -22,5 +22,5 @@ class CreatedMicro(DateTimeMicro):
22
22
  if self.config("utc", silent=True):
23
23
  now = self.datetime.datetime.now(self.datetime.timezone.utc)
24
24
  else:
25
- now = self.datetime.datetime.now()
25
+ now = self.datetime.datetime.now(self._timezone)
26
26
  return {**data, self.name: now}
@@ -3,7 +3,6 @@ from datetime import datetime, timezone
3
3
  import dateparser
4
4
  from ..autodoc.schema import DateTime as AutoDocDateTime
5
5
 
6
-
7
6
  class DateTime(Column):
8
7
  _auto_doc_class = AutoDocDateTime
9
8
  _date_format = "%Y-%m-%d %H:%M:%S"
@@ -14,8 +13,9 @@ class DateTime(Column):
14
13
  "default_date",
15
14
  ]
16
15
 
17
- def __init__(self, di):
16
+ def __init__(self, di, timezone: datetime.tzinfo):
18
17
  super().__init__(di)
18
+ self._timezone = timezone
19
19
 
20
20
  def _finalize_configuration(self, configuration):
21
21
  return {
@@ -33,7 +33,7 @@ class DateTime(Column):
33
33
  date = dateparser.parse(value)
34
34
  else:
35
35
  date = value
36
- return date.replace(tzinfo=timezone.utc) if date else None
36
+ return date.replace(tzinfo=self._timezone) if date else None
37
37
 
38
38
  def to_backend(self, data):
39
39
  if not self.name in data or type(data[self.name]) == str or data[self.name] == None:
@@ -47,7 +47,7 @@ class DateTime(Column):
47
47
  return {self.name: datetime.isoformat() if datetime else None}
48
48
 
49
49
  def build_condition(self, value, operator=None, column_prefix=""):
50
- date = dateparser.parse(value).astimezone(timezone.utc).strftime(self.config("date_format"))
50
+ date = dateparser.parse(value).astimezone(self._timezone).strftime(self.config("date_format"))
51
51
  if not operator:
52
52
  operator = "="
53
53
  return f"{column_prefix}{self.name}{operator}{date}"
@@ -1,3 +1,4 @@
1
+ from re import T
1
2
  from .datetime import DateTime
2
3
  from datetime import datetime, timezone
3
4
  import dateparser
@@ -8,5 +9,5 @@ class DateTimeMicro(DateTime):
8
9
  _date_format = "%Y-%m-%d %H:%M:%S.%f"
9
10
  _default_date = "0000-00-00 00:00:00.000000"
10
11
 
11
- def __init__(self, di):
12
- super().__init__(di)
12
+ def __init__(self, di, timezone: datetime.tzinfo):
13
+ super().__init__(di, timezone)
@@ -1,3 +1,4 @@
1
+ import time
1
2
  from .datetime import DateTime
2
3
  from datetime import datetime, timezone
3
4
  import dateparser
@@ -28,16 +29,16 @@ class Timestamp(DateTime):
28
29
  raise ValueError(
29
30
  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}'"
30
31
  )
31
- date = datetime.fromtimestamp(int(value) / mult)
32
+ date = datetime.fromtimestamp(int(value) / mult, self._timezone)
32
33
  elif isinstance(value, int):
33
- date = datetime.fromtimestamp(value / mult)
34
+ date = datetime.fromtimestamp(value / mult, self._timezone)
34
35
  else:
35
36
  if not isinstance(value, datetime):
36
37
  raise ValueError(
37
38
  f"Invalid data was found in the backend for model {self.model_class.__name__} and column {self.name}: the value was neither an integer, a string, nor a datetime object"
38
39
  )
39
40
  date = value
40
- return date.replace(tzinfo=timezone.utc) if date else None
41
+ return date.replace(tzinfo=self._timezone) if date else None
41
42
 
42
43
  def to_backend(self, data):
43
44
  if not self.name in data or isinstance(data[self.name], int) or data[self.name] == None:
@@ -1,3 +1,5 @@
1
+ import datetime
2
+
1
3
  from .datetime import DateTime
2
4
 
3
5
 
@@ -8,8 +10,8 @@ class Updated(DateTime):
8
10
  "utc",
9
11
  ]
10
12
 
11
- def __init__(self, di, datetime):
12
- super().__init__(di)
13
+ def __init__(self, di, datetime, timezone: datetime.tzinfo):
14
+ super().__init__(di, timezone)
13
15
  self.datetime = datetime
14
16
 
15
17
  @property
@@ -20,5 +22,5 @@ class Updated(DateTime):
20
22
  if self.config("utc", silent=True):
21
23
  now = self.datetime.datetime.now(self.datetime.timezone.utc)
22
24
  else:
23
- now = self.datetime.datetime.now()
25
+ now = self.datetime.datetime.now(self._timezone)
24
26
  return {**data, self.name: now}
@@ -1,3 +1,5 @@
1
+ import datetime
2
+
1
3
  from .datetime_micro import DateTimeMicro
2
4
 
3
5
 
@@ -8,8 +10,8 @@ class UpdatedMicro(DateTimeMicro):
8
10
  "utc",
9
11
  ]
10
12
 
11
- def __init__(self, di, datetime):
12
- super().__init__(di)
13
+ def __init__(self, di, datetime, timezone: datetime.tzinfo):
14
+ super().__init__(di, timezone)
13
15
  self.datetime = datetime
14
16
 
15
17
  @property
@@ -20,5 +22,5 @@ class UpdatedMicro(DateTimeMicro):
20
22
  if self.config("utc", silent=True):
21
23
  now = self.datetime.datetime.now(self.datetime.timezone.utc)
22
24
  else:
23
- now = self.datetime.datetime.now()
25
+ now = self.datetime.datetime.now(self._timezone)
24
26
  return {**data, self.name: now}
@@ -138,3 +138,8 @@ class StandardDependencies(DI):
138
138
 
139
139
  def provide_uuid(self):
140
140
  return uuid
141
+
142
+ def provide_timezone(self):
143
+ """Set the default timezone."""
144
+ import datetime
145
+ return datetime.UTC
clearskies/model.py CHANGED
@@ -5,6 +5,10 @@ from .functional import string
5
5
  import re
6
6
  from .models import Models
7
7
 
8
+ try:
9
+ from typing_extensions import Self
10
+ except ModuleNotFoundError:
11
+ from typing import Self
8
12
 
9
13
  class Model(Models):
10
14
  _configured_columns = None
@@ -14,14 +18,14 @@ class Model(Models):
14
18
  _transformed = None
15
19
  id_column_name = "id"
16
20
 
17
- def __init__(self, backend, columns):
21
+ def __init__(self: Self, backend, columns):
18
22
  super().__init__(backend, columns)
19
23
  self._transformed = {}
20
24
  self._data = {}
21
25
  self._previous_data = None
22
26
  self._touched_columns = None
23
27
 
24
- def model_class(self):
28
+ def model_class(self: Self) -> type[Self]:
25
29
  """
26
30
  Return the model class that this models object will find/return instances of
27
31
 
@@ -30,7 +34,7 @@ class Model(Models):
30
34
  return self.__class__
31
35
 
32
36
  @classmethod
33
- def table_name(cls):
37
+ def table_name(cls: type[Self]) -> str:
34
38
  """Return the name of the table that the model uses for data storage"""
35
39
  singular = string.camel_case_to_snake_case(cls.__name__)
36
40
  if singular[-1] == "y":
@@ -40,16 +44,16 @@ class Model(Models):
40
44
  return f"{singular}s"
41
45
 
42
46
  @abstractmethod
43
- def columns_configuration(self):
47
+ def columns_configuration(self: Self):
44
48
  """Returns an ordered dictionary with the configuration for the columns"""
45
49
  pass
46
50
 
47
- def all_columns(self):
51
+ def all_columns(self: Self):
48
52
  default = OrderedDict([(self.id_column_name, {"class": UUID})])
49
53
  default.update(self.columns_configuration())
50
54
  return default
51
55
 
52
- def columns(self, overrides=None):
56
+ def columns(self: Self, overrides=None):
53
57
  # no caching if we have overrides
54
58
  if overrides is not None:
55
59
  return self._columns.configure(self.all_columns(), self.__class__, overrides=overrides)
@@ -58,13 +62,13 @@ class Model(Models):
58
62
  self._configured_columns = self._columns.configure(self.all_columns(), self.__class__)
59
63
  return self._configured_columns
60
64
 
61
- def supports_n_plus_one(self):
65
+ def supports_n_plus_one(self: Self):
62
66
  return self._backend.supports_n_plus_one
63
67
 
64
- def __getitem__(self, column_name):
68
+ def __getitem__(self: Self, column_name):
65
69
  return self.__getattr__(column_name)
66
70
 
67
- def __getattr__(self, column_name):
71
+ def __getattr__(self: Self, column_name):
68
72
  # this should be adjusted to only return None for empty records if the column name corresponds
69
73
  # to an actual column in the table.
70
74
  if not self.exists:
@@ -72,13 +76,13 @@ class Model(Models):
72
76
 
73
77
  return self.get_transformed_from_data(column_name, self._data)
74
78
 
75
- def get(self, column_name, silent=False):
79
+ def get(self: Self, column_name, silent=False):
76
80
  if not self.exists:
77
81
  return None
78
82
 
79
83
  return self.get_transformed_from_data(column_name, self._data, silent=silent)
80
84
 
81
- def get_transformed_from_data(self, column_name, data, cache=True, check_providers=True, silent=False):
85
+ def get_transformed_from_data(self: Self, column_name, data, cache=True, check_providers=True, silent=False):
82
86
  if cache and column_name in self._transformed:
83
87
  return self._transformed[column_name]
84
88
 
@@ -111,18 +115,18 @@ class Model(Models):
111
115
  return value
112
116
 
113
117
  @property
114
- def exists(self):
118
+ def exists(self: Self) -> bool:
115
119
  return True if (self.id_column_name in self._data and self._data[self.id_column_name]) else False
116
120
 
117
121
  @property
118
- def data(self):
122
+ def data(self: Self):
119
123
  return self._data
120
124
 
121
125
  @data.setter
122
- def data(self, data):
126
+ def data(self: Self, data) -> None:
123
127
  self._data = {} if data is None else data
124
128
 
125
- def save(self, data, columns=None):
129
+ def save(self: Self, data, columns=None) -> bool:
126
130
  """
127
131
  Save data to the database and update the model!
128
132
 
@@ -168,7 +172,7 @@ class Model(Models):
168
172
 
169
173
  return True
170
174
 
171
- def is_changing(self, key, data):
175
+ def is_changing(self: Self, key, data) -> bool:
172
176
  """
173
177
  Returns True/False to denote if the given column is being modified by the active save operation
174
178
 
@@ -184,7 +188,7 @@ class Model(Models):
184
188
 
185
189
  return self.__getattr__(key) != data[key]
186
190
 
187
- def latest(self, key, data):
191
+ def latest(self: Self, key, data):
188
192
  """
189
193
  Returns the 'latest' value for a column during the save operation
190
194
 
@@ -198,7 +202,7 @@ class Model(Models):
198
202
  return data[key]
199
203
  return self.__getattr__(key)
200
204
 
201
- def was_changed(self, key):
205
+ def was_changed(self: Self, key) -> bool:
202
206
  """Returns True/False to denote if a column was changed in the last save"""
203
207
  if self._previous_data is None:
204
208
  raise ValueError("was_changed was called before a save was finished - you must save something first")
@@ -221,10 +225,10 @@ class Model(Models):
221
225
  return old_value != new_value
222
226
  return not columns[key].values_match(old_value, new_value)
223
227
 
224
- def previous_value(self, key):
228
+ def previous_value(self: Self, key):
225
229
  return self.get_transformed_from_data(key, self._previous_data, cache=False, check_providers=False, silent=True)
226
230
 
227
- def delete(self, except_if_not_exists=True):
231
+ def delete(self: Self, except_if_not_exists=True) -> bool:
228
232
  if not self.exists:
229
233
  if except_if_not_exists:
230
234
  raise ValueError("Cannot delete model that already exists")
@@ -240,7 +244,7 @@ class Model(Models):
240
244
  self.post_delete()
241
245
  return True
242
246
 
243
- def columns_pre_save(self, data, columns):
247
+ def columns_pre_save(self: Self, data, columns):
244
248
  """Uses the column information present in the model to make any necessary changes before saving"""
245
249
  for column in columns.values():
246
250
  data = column.pre_save(data, self)
@@ -258,7 +262,7 @@ class Model(Models):
258
262
  """
259
263
  return data
260
264
 
261
- def columns_to_backend(self, data, columns):
265
+ def columns_to_backend(self: Self, data, columns):
262
266
  backend_data = {**data}
263
267
  temporary_data = {}
264
268
  for column in columns.values():
@@ -275,10 +279,10 @@ class Model(Models):
275
279
 
276
280
  return [backend_data, temporary_data]
277
281
 
278
- def to_backend(self, data, columns):
282
+ def to_backend(self: Self, data, columns):
279
283
  return data
280
284
 
281
- def columns_post_save(self, data, id, columns):
285
+ def columns_post_save(self: Self, data, id, columns):
282
286
  """Uses the column information present in the model to make additional changes as needed after saving"""
283
287
  for column in columns.values():
284
288
  data = column.post_save(data, self, id)
@@ -288,12 +292,12 @@ class Model(Models):
288
292
  )
289
293
  return data
290
294
 
291
- def columns_save_finished(self, columns):
295
+ def columns_save_finished(self: Self, columns):
292
296
  """Calls the save_finished method on all of our columns"""
293
297
  for column in columns.values():
294
298
  column.save_finished(self)
295
299
 
296
- def post_save(self, data, id):
300
+ def post_save(self: Self, data, id):
297
301
  """
298
302
  A hook to extend so you can provide additional pre-save logic as needed
299
303
 
@@ -302,7 +306,7 @@ class Model(Models):
302
306
  """
303
307
  pass
304
308
 
305
- def pre_save(self, data):
309
+ def pre_save(self: Self, data):
306
310
  """
307
311
  A hook to extend so you can provide additional pre-save logic as needed
308
312
 
@@ -310,7 +314,7 @@ class Model(Models):
310
314
  """
311
315
  return data
312
316
 
313
- def save_finished(self):
317
+ def save_finished(self: Self):
314
318
  """
315
319
  A hook to extend so you can provide additional logic after a save operation has fully completed
316
320
 
@@ -320,29 +324,29 @@ class Model(Models):
320
324
  """
321
325
  pass
322
326
 
323
- def columns_pre_delete(self, columns):
327
+ def columns_pre_delete(self: Self, columns):
324
328
  """Uses the column information present in the model to make any necessary changes before deleting"""
325
329
  for column in columns.values():
326
330
  column.pre_delete(self)
327
331
 
328
- def pre_delete(self):
332
+ def pre_delete(self: Self):
329
333
  """
330
334
  A hook to extend so you can provide additional pre-delete logic as needed
331
335
  """
332
336
  pass
333
337
 
334
- def columns_post_delete(self, columns):
338
+ def columns_post_delete(self: Self, columns):
335
339
  """Uses the column information present in the model to make any necessary changes after deleting"""
336
340
  for column in columns.values():
337
341
  column.post_delete(self)
338
342
 
339
- def post_delete(self):
343
+ def post_delete(self: Self):
340
344
  """
341
345
  A hook to extend so you can provide additional post-delete logic as needed
342
346
  """
343
347
  pass
344
348
 
345
- def where_for_request(self, models, routing_data, authorization_data, input_output, overrides=None):
349
+ def where_for_request(self: Self, models, routing_data, authorization_data, input_output, overrides=None):
346
350
  """
347
351
  A hook to automatically apply filtering whenever the model makes an appearance in a get/update/list/search handler.
348
352
  """
clearskies/models.py CHANGED
@@ -47,30 +47,30 @@ class Models(ABC, ConditionParser):
47
47
  self.query_select_all = True
48
48
 
49
49
  @abstractmethod
50
- def model_class(self):
50
+ def model_class(self: Self) -> type[Self]:
51
51
  """Return the model class that this models object will find/return instances of"""
52
52
  pass
53
53
 
54
- def clone(self) -> Self:
54
+ def clone(self: Self) -> Self:
55
55
  clone = self.blank()
56
56
  clone.query_configuration = self.query_configuration
57
57
  return clone
58
58
 
59
- def blank(self) -> Self:
59
+ def blank(self: Self) -> Self:
60
60
  return self._build_model()
61
61
 
62
- def get_table_name(self) -> str:
62
+ def get_table_name(self: Self) -> str:
63
63
  if self._table_name is None:
64
64
  self._table_name = self.model_class().table_name()
65
65
  return self._table_name
66
66
 
67
- def get_id_column_name(self) -> str:
67
+ def get_id_column_name(self: Self) -> str:
68
68
  if self._id_column_name is None:
69
69
  self._id_column_name = self.empty_model().id_column_name
70
70
  return self._id_column_name
71
71
 
72
72
  @property
73
- def query_configuration(self) -> Dict[str, Any]:
73
+ def query_configuration(self: Self) -> Dict[str, Any]:
74
74
  return {
75
75
  "wheres": [*self.query_wheres],
76
76
  "sorts": [*self.query_sorts],
@@ -85,7 +85,7 @@ class Models(ABC, ConditionParser):
85
85
  }
86
86
 
87
87
  @query_configuration.setter
88
- def query_configuration(self, configuration: Dict[str, Any]):
88
+ def query_configuration(self: Self, configuration: Dict[str, Any]):
89
89
  self.query_wheres = configuration["wheres"]
90
90
  self.query_sorts = configuration["sorts"]
91
91
  self.query_group_by_column = configuration["group_by_column"]
@@ -97,34 +97,34 @@ class Models(ABC, ConditionParser):
97
97
  self._model_columns = configuration["model_columns"]
98
98
 
99
99
  @property
100
- def model_columns(self):
100
+ def model_columns(self: Self):
101
101
  if self._model_columns is None:
102
102
  self._model_columns = self.empty_model().columns()
103
103
  return self._model_columns
104
104
 
105
- def select(self, selects) -> Self:
105
+ def select(self: Self, selects) -> Self:
106
106
  return self.clone().select_in_place(selects)
107
107
 
108
- def select_in_place(self, selects) -> Self:
108
+ def select_in_place(self: Self, selects) -> Self:
109
109
  self.query_selects.append(selects)
110
110
  self.must_rexecute = True
111
111
  self._next_page_data = None
112
112
  return self
113
113
 
114
- def select_all(self, select_all=True) -> Self:
114
+ def select_all(self: Self, select_all=True) -> Self:
115
115
  return self.clone().select_all_in_place(select_all=select_all)
116
116
 
117
- def select_all_in_place(self, select_all=True) -> Self:
117
+ def select_all_in_place(self: Self, select_all=True) -> Self:
118
118
  self.query_select_all = select_all
119
119
  self.must_rexecute = True
120
120
  self._next_page_data = None
121
121
  return self
122
122
 
123
- def where(self, where: str) -> Self:
123
+ def where(self: Self, where: str) -> Self:
124
124
  """Adds the given condition to the query and returns a new Models object"""
125
125
  return self.clone().where_in_place(where)
126
126
 
127
- def where_in_place(self, where: str) -> Self:
127
+ def where_in_place(self: Self, where: str) -> Self:
128
128
  """Adds the given condition to the query for the current Models object"""
129
129
  condition = self.parse_condition(where)
130
130
  self._validate_column(condition["column"], "filter", table=condition["table"])
@@ -134,17 +134,17 @@ class Models(ABC, ConditionParser):
134
134
  self.must_recount = True
135
135
  return self
136
136
 
137
- def join(self, join: str) -> Self:
137
+ def join(self: Self, join: str) -> Self:
138
138
  return self.clone().join_in_place(join)
139
139
 
140
- def join_in_place(self, join: str) -> Self:
140
+ def join_in_place(self: Self, join: str) -> Self:
141
141
  self.query_joins.append(self.parse_join(join))
142
142
  self.must_rexecute = True
143
143
  self._next_page_data = None
144
144
  self.must_recount = True
145
145
  return self
146
146
 
147
- def is_joined(self, table_name, alias=None):
147
+ def is_joined(self: Self, table_name, alias=None):
148
148
  for join in self.query_joins:
149
149
  if join["table"] != table_name:
150
150
  continue
@@ -155,10 +155,10 @@ class Models(ABC, ConditionParser):
155
155
  return join["alias"] if join["alias"] else join["table"]
156
156
  return False
157
157
 
158
- def group_by(self, group_column: str) -> Self:
158
+ def group_by(self: Self, group_column: str) -> Self:
159
159
  return self.clone().group_by_in_place(group_column)
160
160
 
161
- def group_by_in_place(self, group_column: str) -> Self:
161
+ def group_by_in_place(self: Self, group_column: str) -> Self:
162
162
  self._validate_column(group_column, "group")
163
163
  self.query_group_by_column = group_column
164
164
  self.must_rexecute = True
@@ -167,7 +167,7 @@ class Models(ABC, ConditionParser):
167
167
  return self
168
168
 
169
169
  def sort_by(
170
- self,
170
+ self: Self,
171
171
  primary_column,
172
172
  primary_direction,
173
173
  primary_table=None,
@@ -185,7 +185,7 @@ class Models(ABC, ConditionParser):
185
185
  )
186
186
 
187
187
  def sort_by_in_place(
188
- self,
188
+ self: Self,
189
189
  primary_column,
190
190
  primary_direction,
191
191
  primary_table=None,
@@ -205,7 +205,7 @@ class Models(ABC, ConditionParser):
205
205
  self._next_page_data = None
206
206
  return self
207
207
 
208
- def _normalize_and_validate_sort(self, sort):
208
+ def _normalize_and_validate_sort(self: Self, sort):
209
209
  if "column" not in sort or not sort["column"]:
210
210
  raise ValueError("Missing 'column' for sort")
211
211
  if "direction" not in sort or not sort["direction"]:
@@ -218,7 +218,7 @@ class Models(ABC, ConditionParser):
218
218
  # down the line we may ask the model class what columns we can sort on, but we're good for now
219
219
  return {"column": sort["column"], "direction": sort["direction"], "table": sort.get("table")}
220
220
 
221
- def _validate_column(self, column_name, action, table=None):
221
+ def _validate_column(self: Self, column_name, action, table=None):
222
222
  """
223
223
  Down the line we may use the model configuration to check what columns are valid sort/group/search targets
224
224
  """
@@ -246,19 +246,19 @@ class Models(ABC, ConditionParser):
246
246
  + "to your model definition"
247
247
  )
248
248
 
249
- def limit(self, limit) -> Self:
249
+ def limit(self: Self, limit) -> Self:
250
250
  return self.clone().limit_in_place(limit)
251
251
 
252
- def limit_in_place(self, limit) -> Self:
252
+ def limit_in_place(self: Self, limit) -> Self:
253
253
  self.query_limit = limit
254
254
  self.must_rexecute = True
255
255
  self._next_page_data = None
256
256
  return self
257
257
 
258
- def pagination(self, **kwargs) -> Self:
258
+ def pagination(self: Self, **kwargs) -> Self:
259
259
  return self.clone().pagination_in_place(**kwargs)
260
260
 
261
- def pagination_in_place(self, **kwargs) -> Self:
261
+ def pagination_in_place(self: Self, **kwargs) -> Self:
262
262
  error = self._backend.validate_pagination_kwargs(kwargs, str)
263
263
  if error:
264
264
  raise ValueError(
@@ -270,17 +270,17 @@ class Models(ABC, ConditionParser):
270
270
  self._next_page_data = None
271
271
  return self
272
272
 
273
- def find(self, where: str) -> Self:
273
+ def find(self: Self, where: str) -> Self:
274
274
  """Returns the first model where condition"""
275
275
  return self.clone().where(where).first()
276
276
 
277
- def __len__(self):
277
+ def __len__(self: Self):
278
278
  if self.must_recount:
279
279
  self.count = self._backend.count(self.query_configuration, self.empty_model())
280
280
  self.must_recount = False
281
281
  return self.count
282
282
 
283
- def __iter__(self) -> Iterator[Self]:
283
+ def __iter__(self: Self) -> Iterator[Self]:
284
284
  self._next_page_data = {}
285
285
  raw_rows = self._backend.records(
286
286
  self.query_configuration,
@@ -290,7 +290,7 @@ class Models(ABC, ConditionParser):
290
290
  models = iter([self.model(row) for row in raw_rows])
291
291
  return models
292
292
 
293
- def paginate_all(self) -> List[Self]:
293
+ def paginate_all(self: Self) -> List[Self]:
294
294
  next_models = self.clone()
295
295
  results = list(next_models.__iter__())
296
296
  next_page_data = next_models.next_page_data()
@@ -300,51 +300,51 @@ class Models(ABC, ConditionParser):
300
300
  next_page_data = next_models.next_page_data()
301
301
  return results
302
302
 
303
- def model(self, data) -> Self:
303
+ def model(self: Self, data) -> Self:
304
304
  model = self._build_model()
305
305
  model.data = data
306
306
  return model
307
307
 
308
- def _build_model(self) -> Self:
308
+ def _build_model(self: Self) -> Self:
309
309
  model_class = self.model_class()
310
310
  return model_class(self._backend, self._columns)
311
311
 
312
- def empty_model(self) -> Self:
312
+ def empty_model(self: Self) -> Self:
313
313
  return self.model({})
314
314
 
315
- def create(self, data: Dict[str, Any]) -> Self:
315
+ def create(self: Self, data: Dict[str, Any]) -> Self:
316
316
  empty = self.empty_model()
317
317
  empty.save(data)
318
318
  return empty
319
319
 
320
- def first(self) -> Self:
320
+ def first(self: Self) -> Self:
321
321
  iter = self.__iter__()
322
322
  try:
323
323
  return iter.__next__()
324
324
  except StopIteration:
325
325
  return self.empty_model()
326
326
 
327
- def columns(self, overrides=None):
327
+ def columns(self: Self, overrides=None):
328
328
  model = self.model({})
329
329
  return model.columns(overrides=None)
330
330
 
331
- def raw_columns_configuration(self):
331
+ def raw_columns_configuration(self: Self):
332
332
  return self.model({}).all_columns()
333
333
 
334
- def allowed_pagination_keys(self) -> List[str]:
334
+ def allowed_pagination_keys(self: Self) -> List[str]:
335
335
  return self._backend.allowed_pagination_keys()
336
336
 
337
337
  def validate_pagination_kwargs(self, kwargs: Dict[str, Any], case_mapping: Callable) -> str:
338
338
  return self._backend.validate_pagination_kwargs(kwargs, case_mapping)
339
339
 
340
- def next_page_data(self):
340
+ def next_page_data(self: Self):
341
341
  return self._next_page_data
342
342
 
343
- def documentation_pagination_next_page_response(self, case_mapping: Callable) -> List[Any]:
343
+ def documentation_pagination_next_page_response(self: Self, case_mapping: Callable) -> List[Any]:
344
344
  return self._backend.documentation_pagination_next_page_response(case_mapping)
345
345
 
346
- def documentation_pagination_next_page_example(self, case_mapping: Callable) -> Dict[str, Any]:
346
+ def documentation_pagination_next_page_example(self: Self, case_mapping: Callable) -> Dict[str, Any]:
347
347
  return self._backend.documentation_pagination_next_page_example(case_mapping)
348
348
 
349
- def documentation_pagination_parameters(self, case_mapping: Callable) -> List[Tuple[Any]]:
349
+ def documentation_pagination_parameters(self: Self, case_mapping: Callable) -> List[Tuple[Any]]:
350
350
  return self._backend.documentation_pagination_parameters(case_mapping)