clear-skies 2.0.27__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 (270) hide show
  1. clear_skies-2.0.27.dist-info/METADATA +78 -0
  2. clear_skies-2.0.27.dist-info/RECORD +270 -0
  3. clear_skies-2.0.27.dist-info/WHEEL +4 -0
  4. clear_skies-2.0.27.dist-info/licenses/LICENSE +7 -0
  5. clearskies/__init__.py +69 -0
  6. clearskies/action.py +7 -0
  7. clearskies/authentication/__init__.py +15 -0
  8. clearskies/authentication/authentication.py +44 -0
  9. clearskies/authentication/authorization.py +23 -0
  10. clearskies/authentication/authorization_pass_through.py +22 -0
  11. clearskies/authentication/jwks.py +165 -0
  12. clearskies/authentication/public.py +5 -0
  13. clearskies/authentication/secret_bearer.py +551 -0
  14. clearskies/autodoc/__init__.py +8 -0
  15. clearskies/autodoc/formats/__init__.py +5 -0
  16. clearskies/autodoc/formats/oai3_json/__init__.py +7 -0
  17. clearskies/autodoc/formats/oai3_json/oai3_json.py +87 -0
  18. clearskies/autodoc/formats/oai3_json/oai3_schema_resolver.py +15 -0
  19. clearskies/autodoc/formats/oai3_json/parameter.py +35 -0
  20. clearskies/autodoc/formats/oai3_json/request.py +68 -0
  21. clearskies/autodoc/formats/oai3_json/response.py +28 -0
  22. clearskies/autodoc/formats/oai3_json/schema/__init__.py +11 -0
  23. clearskies/autodoc/formats/oai3_json/schema/array.py +9 -0
  24. clearskies/autodoc/formats/oai3_json/schema/default.py +13 -0
  25. clearskies/autodoc/formats/oai3_json/schema/enum.py +7 -0
  26. clearskies/autodoc/formats/oai3_json/schema/object.py +35 -0
  27. clearskies/autodoc/formats/oai3_json/test.json +1985 -0
  28. clearskies/autodoc/py.typed +0 -0
  29. clearskies/autodoc/request/__init__.py +15 -0
  30. clearskies/autodoc/request/header.py +6 -0
  31. clearskies/autodoc/request/json_body.py +6 -0
  32. clearskies/autodoc/request/parameter.py +8 -0
  33. clearskies/autodoc/request/request.py +47 -0
  34. clearskies/autodoc/request/url_parameter.py +6 -0
  35. clearskies/autodoc/request/url_path.py +6 -0
  36. clearskies/autodoc/response/__init__.py +5 -0
  37. clearskies/autodoc/response/response.py +9 -0
  38. clearskies/autodoc/schema/__init__.py +31 -0
  39. clearskies/autodoc/schema/array.py +10 -0
  40. clearskies/autodoc/schema/base64.py +8 -0
  41. clearskies/autodoc/schema/boolean.py +5 -0
  42. clearskies/autodoc/schema/date.py +5 -0
  43. clearskies/autodoc/schema/datetime.py +5 -0
  44. clearskies/autodoc/schema/double.py +5 -0
  45. clearskies/autodoc/schema/enum.py +17 -0
  46. clearskies/autodoc/schema/integer.py +6 -0
  47. clearskies/autodoc/schema/long.py +5 -0
  48. clearskies/autodoc/schema/number.py +6 -0
  49. clearskies/autodoc/schema/object.py +13 -0
  50. clearskies/autodoc/schema/password.py +5 -0
  51. clearskies/autodoc/schema/schema.py +11 -0
  52. clearskies/autodoc/schema/string.py +5 -0
  53. clearskies/backends/__init__.py +67 -0
  54. clearskies/backends/api_backend.py +1194 -0
  55. clearskies/backends/backend.py +137 -0
  56. clearskies/backends/cursor_backend.py +339 -0
  57. clearskies/backends/graphql_backend.py +977 -0
  58. clearskies/backends/memory_backend.py +794 -0
  59. clearskies/backends/secrets_backend.py +100 -0
  60. clearskies/clients/__init__.py +5 -0
  61. clearskies/clients/graphql_client.py +182 -0
  62. clearskies/column.py +1221 -0
  63. clearskies/columns/__init__.py +71 -0
  64. clearskies/columns/audit.py +306 -0
  65. clearskies/columns/belongs_to_id.py +478 -0
  66. clearskies/columns/belongs_to_model.py +145 -0
  67. clearskies/columns/belongs_to_self.py +109 -0
  68. clearskies/columns/boolean.py +110 -0
  69. clearskies/columns/category_tree.py +274 -0
  70. clearskies/columns/category_tree_ancestors.py +51 -0
  71. clearskies/columns/category_tree_children.py +125 -0
  72. clearskies/columns/category_tree_descendants.py +48 -0
  73. clearskies/columns/created.py +92 -0
  74. clearskies/columns/created_by_authorization_data.py +114 -0
  75. clearskies/columns/created_by_header.py +103 -0
  76. clearskies/columns/created_by_ip.py +90 -0
  77. clearskies/columns/created_by_routing_data.py +102 -0
  78. clearskies/columns/created_by_user_agent.py +89 -0
  79. clearskies/columns/date.py +232 -0
  80. clearskies/columns/datetime.py +284 -0
  81. clearskies/columns/email.py +78 -0
  82. clearskies/columns/float.py +149 -0
  83. clearskies/columns/has_many.py +552 -0
  84. clearskies/columns/has_many_self.py +62 -0
  85. clearskies/columns/has_one.py +21 -0
  86. clearskies/columns/integer.py +158 -0
  87. clearskies/columns/json.py +126 -0
  88. clearskies/columns/many_to_many_ids.py +335 -0
  89. clearskies/columns/many_to_many_ids_with_data.py +281 -0
  90. clearskies/columns/many_to_many_models.py +163 -0
  91. clearskies/columns/many_to_many_pivots.py +132 -0
  92. clearskies/columns/phone.py +162 -0
  93. clearskies/columns/select.py +95 -0
  94. clearskies/columns/string.py +102 -0
  95. clearskies/columns/timestamp.py +164 -0
  96. clearskies/columns/updated.py +107 -0
  97. clearskies/columns/uuid.py +83 -0
  98. clearskies/configs/README.md +105 -0
  99. clearskies/configs/__init__.py +170 -0
  100. clearskies/configs/actions.py +43 -0
  101. clearskies/configs/any.py +15 -0
  102. clearskies/configs/any_dict.py +24 -0
  103. clearskies/configs/any_dict_or_callable.py +25 -0
  104. clearskies/configs/authentication.py +23 -0
  105. clearskies/configs/authorization.py +23 -0
  106. clearskies/configs/boolean.py +18 -0
  107. clearskies/configs/boolean_or_callable.py +20 -0
  108. clearskies/configs/callable_config.py +20 -0
  109. clearskies/configs/columns.py +34 -0
  110. clearskies/configs/conditions.py +30 -0
  111. clearskies/configs/config.py +26 -0
  112. clearskies/configs/datetime.py +20 -0
  113. clearskies/configs/datetime_or_callable.py +21 -0
  114. clearskies/configs/email.py +10 -0
  115. clearskies/configs/email_list.py +17 -0
  116. clearskies/configs/email_list_or_callable.py +17 -0
  117. clearskies/configs/email_or_email_list_or_callable.py +59 -0
  118. clearskies/configs/endpoint.py +23 -0
  119. clearskies/configs/endpoint_list.py +29 -0
  120. clearskies/configs/float.py +18 -0
  121. clearskies/configs/float_or_callable.py +20 -0
  122. clearskies/configs/headers.py +28 -0
  123. clearskies/configs/integer.py +18 -0
  124. clearskies/configs/integer_or_callable.py +20 -0
  125. clearskies/configs/joins.py +30 -0
  126. clearskies/configs/list_any_dict.py +32 -0
  127. clearskies/configs/list_any_dict_or_callable.py +33 -0
  128. clearskies/configs/model_class.py +35 -0
  129. clearskies/configs/model_column.py +67 -0
  130. clearskies/configs/model_columns.py +58 -0
  131. clearskies/configs/model_destination_name.py +26 -0
  132. clearskies/configs/model_to_id_column.py +45 -0
  133. clearskies/configs/readable_model_column.py +11 -0
  134. clearskies/configs/readable_model_columns.py +11 -0
  135. clearskies/configs/schema.py +23 -0
  136. clearskies/configs/searchable_model_columns.py +11 -0
  137. clearskies/configs/security_headers.py +39 -0
  138. clearskies/configs/select.py +28 -0
  139. clearskies/configs/select_list.py +49 -0
  140. clearskies/configs/string.py +31 -0
  141. clearskies/configs/string_dict.py +34 -0
  142. clearskies/configs/string_list.py +47 -0
  143. clearskies/configs/string_list_or_callable.py +48 -0
  144. clearskies/configs/string_or_callable.py +18 -0
  145. clearskies/configs/timedelta.py +20 -0
  146. clearskies/configs/timezone.py +20 -0
  147. clearskies/configs/url.py +25 -0
  148. clearskies/configs/validators.py +45 -0
  149. clearskies/configs/writeable_model_column.py +11 -0
  150. clearskies/configs/writeable_model_columns.py +11 -0
  151. clearskies/configurable.py +78 -0
  152. clearskies/contexts/__init__.py +11 -0
  153. clearskies/contexts/cli.py +130 -0
  154. clearskies/contexts/context.py +99 -0
  155. clearskies/contexts/wsgi.py +79 -0
  156. clearskies/contexts/wsgi_ref.py +87 -0
  157. clearskies/cursors/__init__.py +10 -0
  158. clearskies/cursors/cursor.py +161 -0
  159. clearskies/cursors/from_environment/__init__.py +5 -0
  160. clearskies/cursors/from_environment/mysql.py +51 -0
  161. clearskies/cursors/from_environment/postgresql.py +49 -0
  162. clearskies/cursors/from_environment/sqlite.py +35 -0
  163. clearskies/cursors/mysql.py +61 -0
  164. clearskies/cursors/postgresql.py +61 -0
  165. clearskies/cursors/sqlite.py +62 -0
  166. clearskies/decorators.py +33 -0
  167. clearskies/decorators.pyi +10 -0
  168. clearskies/di/__init__.py +15 -0
  169. clearskies/di/additional_config.py +130 -0
  170. clearskies/di/additional_config_auto_import.py +17 -0
  171. clearskies/di/di.py +948 -0
  172. clearskies/di/inject/__init__.py +25 -0
  173. clearskies/di/inject/akeyless_sdk.py +16 -0
  174. clearskies/di/inject/by_class.py +24 -0
  175. clearskies/di/inject/by_name.py +22 -0
  176. clearskies/di/inject/di.py +16 -0
  177. clearskies/di/inject/environment.py +15 -0
  178. clearskies/di/inject/input_output.py +19 -0
  179. clearskies/di/inject/logger.py +16 -0
  180. clearskies/di/inject/now.py +16 -0
  181. clearskies/di/inject/requests.py +16 -0
  182. clearskies/di/inject/secrets.py +15 -0
  183. clearskies/di/inject/utcnow.py +16 -0
  184. clearskies/di/inject/uuid.py +16 -0
  185. clearskies/di/injectable.py +32 -0
  186. clearskies/di/injectable_properties.py +131 -0
  187. clearskies/end.py +219 -0
  188. clearskies/endpoint.py +1303 -0
  189. clearskies/endpoint_group.py +333 -0
  190. clearskies/endpoints/__init__.py +25 -0
  191. clearskies/endpoints/advanced_search.py +519 -0
  192. clearskies/endpoints/callable.py +382 -0
  193. clearskies/endpoints/create.py +201 -0
  194. clearskies/endpoints/delete.py +133 -0
  195. clearskies/endpoints/get.py +267 -0
  196. clearskies/endpoints/health_check.py +181 -0
  197. clearskies/endpoints/list.py +567 -0
  198. clearskies/endpoints/restful_api.py +417 -0
  199. clearskies/endpoints/schema.py +185 -0
  200. clearskies/endpoints/simple_search.py +279 -0
  201. clearskies/endpoints/update.py +188 -0
  202. clearskies/environment.py +106 -0
  203. clearskies/exceptions/__init__.py +19 -0
  204. clearskies/exceptions/authentication.py +2 -0
  205. clearskies/exceptions/authorization.py +2 -0
  206. clearskies/exceptions/client_error.py +2 -0
  207. clearskies/exceptions/input_errors.py +4 -0
  208. clearskies/exceptions/missing_dependency.py +2 -0
  209. clearskies/exceptions/moved_permanently.py +3 -0
  210. clearskies/exceptions/moved_temporarily.py +3 -0
  211. clearskies/exceptions/not_found.py +2 -0
  212. clearskies/functional/__init__.py +7 -0
  213. clearskies/functional/json.py +47 -0
  214. clearskies/functional/routing.py +92 -0
  215. clearskies/functional/string.py +112 -0
  216. clearskies/functional/validations.py +76 -0
  217. clearskies/input_outputs/__init__.py +13 -0
  218. clearskies/input_outputs/cli.py +157 -0
  219. clearskies/input_outputs/exceptions/__init__.py +7 -0
  220. clearskies/input_outputs/exceptions/cli_input_error.py +2 -0
  221. clearskies/input_outputs/exceptions/cli_not_found.py +2 -0
  222. clearskies/input_outputs/headers.py +54 -0
  223. clearskies/input_outputs/input_output.py +116 -0
  224. clearskies/input_outputs/programmatic.py +62 -0
  225. clearskies/input_outputs/py.typed +0 -0
  226. clearskies/input_outputs/wsgi.py +80 -0
  227. clearskies/loggable.py +19 -0
  228. clearskies/model.py +2039 -0
  229. clearskies/py.typed +0 -0
  230. clearskies/query/__init__.py +12 -0
  231. clearskies/query/condition.py +228 -0
  232. clearskies/query/join.py +136 -0
  233. clearskies/query/query.py +195 -0
  234. clearskies/query/sort.py +27 -0
  235. clearskies/schema.py +82 -0
  236. clearskies/secrets/__init__.py +7 -0
  237. clearskies/secrets/additional_configs/__init__.py +32 -0
  238. clearskies/secrets/additional_configs/mysql_connection_dynamic_producer.py +61 -0
  239. clearskies/secrets/additional_configs/mysql_connection_dynamic_producer_via_ssh_cert_bastion.py +160 -0
  240. clearskies/secrets/akeyless.py +507 -0
  241. clearskies/secrets/exceptions/__init__.py +7 -0
  242. clearskies/secrets/exceptions/not_found_error.py +2 -0
  243. clearskies/secrets/exceptions/permissions_error.py +2 -0
  244. clearskies/secrets/secrets.py +39 -0
  245. clearskies/security_header.py +17 -0
  246. clearskies/security_headers/__init__.py +11 -0
  247. clearskies/security_headers/cache_control.py +68 -0
  248. clearskies/security_headers/cors.py +51 -0
  249. clearskies/security_headers/csp.py +95 -0
  250. clearskies/security_headers/hsts.py +23 -0
  251. clearskies/security_headers/x_content_type_options.py +0 -0
  252. clearskies/security_headers/x_frame_options.py +0 -0
  253. clearskies/typing.py +11 -0
  254. clearskies/validator.py +36 -0
  255. clearskies/validators/__init__.py +33 -0
  256. clearskies/validators/after_column.py +61 -0
  257. clearskies/validators/before_column.py +15 -0
  258. clearskies/validators/in_the_future.py +29 -0
  259. clearskies/validators/in_the_future_at_least.py +13 -0
  260. clearskies/validators/in_the_future_at_most.py +12 -0
  261. clearskies/validators/in_the_past.py +29 -0
  262. clearskies/validators/in_the_past_at_least.py +12 -0
  263. clearskies/validators/in_the_past_at_most.py +12 -0
  264. clearskies/validators/maximum_length.py +25 -0
  265. clearskies/validators/maximum_value.py +28 -0
  266. clearskies/validators/minimum_length.py +25 -0
  267. clearskies/validators/minimum_value.py +28 -0
  268. clearskies/validators/required.py +32 -0
  269. clearskies/validators/timedelta.py +58 -0
  270. clearskies/validators/unique.py +28 -0
@@ -0,0 +1,161 @@
1
+ from abc import ABC
2
+ from types import ModuleType
3
+ from typing import Protocol
4
+
5
+ import clearskies.configs
6
+ from clearskies import configurable, decorators, loggable
7
+ from clearskies.di import InjectableProperties
8
+
9
+
10
+ class DBAPICursor(Protocol):
11
+ """
12
+ A minimal protocol for a DB-API 2.0 cursor.
13
+
14
+ This uses structural subtyping. Any object (like a real cursor
15
+ or a mock) will match this type as long as it has the
16
+ required attributes, even if it doesn't explicitly inherit
17
+ from DBAPICursor.
18
+ """
19
+
20
+ def execute(self, sql: str, parameters: tuple | list):
21
+ """Execute a SQL statement with parameters."""
22
+ ...
23
+
24
+
25
+ class Cursor(ABC, configurable.Configurable, InjectableProperties, loggable.Loggable):
26
+ """
27
+ Abstract base class for database cursor implementations.
28
+
29
+ Provides a common interface for database operations across different
30
+ database backends. Handles connection management, query execution,
31
+ and SQL formatting with configurable escape characters and placeholders.
32
+
33
+ Attributes
34
+ ----------
35
+ database: Name of the database to connect to.
36
+ autocommit: Whether to automatically commit transactions.
37
+ port_forwarding: Optional port forwarding configuration.
38
+ connect_timeout: Connection timeout in seconds.
39
+ table_escape_character: Character used to escape table names.
40
+ column_escape_character: Character used to escape column names.
41
+ value_placeholder: Placeholder character for parameter binding.
42
+ """
43
+
44
+ database = clearskies.configs.String(default="example")
45
+ autocommit = clearskies.configs.Boolean(default=True)
46
+ port_forwarding = clearskies.configs.Any(default=None)
47
+ connect_timeout = clearskies.configs.Integer(default=2)
48
+
49
+ table_escape_character = "`"
50
+ column_escape_character = "`"
51
+ value_placeholder = "%s"
52
+ _cursor: DBAPICursor
53
+ _factory: ModuleType
54
+
55
+ @decorators.parameters_to_properties
56
+ def __init__(
57
+ self,
58
+ database="example",
59
+ autocommit=True,
60
+ port_forwarding=None,
61
+ connect_timeout=2,
62
+ ):
63
+ pass
64
+
65
+ @property
66
+ def factory(self) -> ModuleType:
67
+ """Return the factory for the cursor."""
68
+ return self._factory
69
+
70
+ def build_connection_kwargs(self) -> dict:
71
+ """Return the connection kwargs for the cursor."""
72
+ return {
73
+ "database": self.database,
74
+ "autocommit": self.autocommit,
75
+ "connect_timeout": self.connect_timeout,
76
+ }
77
+
78
+ @property
79
+ def cursor(self) -> DBAPICursor:
80
+ """Get or create a database cursor instance."""
81
+ if not hasattr(self, "_cursor"):
82
+ try:
83
+ if self.port_forwarding:
84
+ self.port_forwarding_context()
85
+ except KeyError:
86
+ pass
87
+
88
+ self._cursor = self.factory.connect(
89
+ **self.build_connection_kwargs(),
90
+ ).cursor()
91
+
92
+ return self._cursor
93
+
94
+ def __call__(self, *args: configurable.Any, **kwds: configurable.Any) -> configurable.Any:
95
+ return self.cursor
96
+
97
+ def __iter__(self):
98
+ """Allow direct iteration over the cursor config."""
99
+ return iter(self())
100
+
101
+ def __next__(self):
102
+ """Allow direct next() calls on the cursor config."""
103
+ return next(self())
104
+
105
+ def port_forwarding_context(self):
106
+ """Context manager for port forwarding (if applicable)."""
107
+ raise NotImplementedError("Port forwarding not implemented for this cursor.")
108
+
109
+ def column_equals_with_placeholder(self, column_name: str) -> str:
110
+ """
111
+ Generate SQL for a column equality check with placeholder.
112
+
113
+ Args:
114
+ column_name: Name of the column to check.
115
+
116
+ Returns
117
+ -------
118
+ SQL string in format: `column`=%s
119
+ """
120
+ return f"{self.column_escape_character}{column_name}{self.column_escape_character}={self.value_placeholder}"
121
+
122
+ def as_sql_with_placeholders(self, table: str, column: str, operator: str, number_values: int = 1) -> str | None:
123
+ """
124
+ Generate SQL condition with placeholders.
125
+
126
+ Args:
127
+ table: Table name.
128
+ column: Column name.
129
+ operator: SQL operator (e.g., '=', '>', '<').
130
+ number_values: Number of value placeholders needed.
131
+
132
+ Returns
133
+ -------
134
+ SQL string if number_values is 1, None otherwise.
135
+ """
136
+ if number_values == 1:
137
+ return f"{table}.{column} {operator} {self.value_placeholder}"
138
+ return None
139
+
140
+ def execute(self, sql: str, parameters: tuple | list = ()):
141
+ """
142
+ Execute a SQL statement with parameters.
143
+
144
+ Args:
145
+ sql: SQL statement to execute.
146
+ parameters: Parameters for the SQL statement.
147
+
148
+ Returns
149
+ -------
150
+ Result of cursor.execute().
151
+ """
152
+ try:
153
+ self.logger.debug(f"Executing SQL: {sql} with parameters: {parameters}")
154
+ return self.cursor.execute(sql, parameters)
155
+ except Exception as e:
156
+ self.logger.exception(f"Error executing SQL: {sql} with parameters: {parameters}")
157
+ raise
158
+
159
+ @property
160
+ def lastrowid(self):
161
+ return getattr(self.cursor, "lastrowid", None)
@@ -0,0 +1,5 @@
1
+ from clearskies.cursors.from_environment.mysql import MySql
2
+ from clearskies.cursors.from_environment.postgresql import Postgresql
3
+ from clearskies.cursors.from_environment.sqlite import Sqlite
4
+
5
+ __all__ = ["MySql", "Postgresql", "Sqlite"]
@@ -0,0 +1,51 @@
1
+ import clearskies.configs
2
+ from clearskies import decorators
3
+ from clearskies.cursors.mysql import Mysql as MysqlBase
4
+ from clearskies.di import inject
5
+
6
+
7
+ class MySql(MysqlBase):
8
+ hostname_environment_key = clearskies.configs.String(default="DATABASE_HOST")
9
+ username_environment_key = clearskies.configs.String(default="DATABASE_USERNAME")
10
+ password_environment_key = clearskies.configs.String(default="DATABASE_PASSWORD")
11
+ database_environment_key = clearskies.configs.String(default="DATABASE_NAME")
12
+
13
+ port_environment_key = clearskies.configs.String(default="DATABASE_PORT")
14
+ cert_path_environment_key = clearskies.configs.String(default="DATABASE_CERT_PATH")
15
+ autocommit_environment_key = clearskies.configs.String(default="DATABASE_AUTOCOMMIT")
16
+ connect_timeout_environment_key = clearskies.configs.String(default="DATABASE_CONNECT_TIMEOUT")
17
+
18
+ environment = inject.Environment()
19
+
20
+ @decorators.parameters_to_properties
21
+ def __init__(
22
+ self,
23
+ hostname_environment_key="DATABASE_HOST",
24
+ username_environment_key="DATABASE_USERNAME",
25
+ password_environment_key="DATABASE_PASSWORD",
26
+ database_environment_key="DATABASE_NAME",
27
+ port_environment_key="DATABASE_PORT",
28
+ cert_path_environment_key="DATABASE_CERT_PATH",
29
+ autocommit_environment_key="DATABASE_AUTOCOMMIT",
30
+ connect_timeout_environment_key="DATABASE_CONNECT_TIMEOUT",
31
+ port_forwarding=None,
32
+ ):
33
+ pass
34
+
35
+ def build_connection_kwargs(self) -> dict:
36
+ connection_kwargs = {
37
+ "user": self.environment.get(self.username_environment_key),
38
+ "password": self.environment.get(self.password_environment_key),
39
+ "host": self.environment.get(self.hostname_environment_key),
40
+ "database": self.environment.get(self.database_environment_key),
41
+ "port": self.environment.get(self.port_environment_key, silent=True),
42
+ "ssl_ca": self.environment.get(self.cert_path_environment_key, silent=True),
43
+ "autocommit": self.environment.get(self.autocommit_environment_key, silent=True),
44
+ "connect_timeout": self.environment.get(self.connect_timeout_environment_key, silent=True),
45
+ }
46
+
47
+ for kwarg in ["autocommit", "connect_timeout", "port", "ssl_ca"]:
48
+ if not connection_kwargs[kwarg]:
49
+ del connection_kwargs[kwarg]
50
+
51
+ return {**super().build_connection_kwargs(), **connection_kwargs}
@@ -0,0 +1,49 @@
1
+ import clearskies.configs
2
+ from clearskies import decorators
3
+ from clearskies.cursors.postgresql import Postgresql as PostgresqlBase
4
+ from clearskies.di import inject
5
+
6
+
7
+ class Postgresql(PostgresqlBase):
8
+ hostname_environment_key = clearskies.configs.String(default="DATABASE_HOST")
9
+ username_environment_key = clearskies.configs.String(default="DATABASE_USERNAME")
10
+ password_environment_key = clearskies.configs.String(default="DATABASE_PASSWORD")
11
+ database_environment_key = clearskies.configs.String(default="DATABASE_NAME")
12
+
13
+ port_environment_key = clearskies.configs.String(default="DATABASE_PORT")
14
+ cert_path_environment_key = clearskies.configs.String(default="DATABASE_CERT_PATH")
15
+ autocommit_environment_key = clearskies.configs.String(default="DATABASE_AUTOCOMMIT")
16
+ connect_timeout_environment_key = clearskies.configs.String(default="DATABASE_CONNECT_TIMEOUT")
17
+
18
+ environment = inject.Environment()
19
+
20
+ @decorators.parameters_to_properties
21
+ def __init__(
22
+ self,
23
+ hostname_environment_key="DATABASE_HOST",
24
+ username_environment_key="DATABASE_USERNAME",
25
+ password_environment_key="DATABASE_PASSWORD",
26
+ database_environment_key="DATABASE_NAME",
27
+ port_environment_key="DATABASE_PORT",
28
+ cert_path_environment_key="DATABASE_CERT_PATH",
29
+ autocommit_environment_key="DATABASE_AUTOCOMMIT",
30
+ port_forwarding=None,
31
+ ):
32
+ pass
33
+
34
+ def build_connection_kwargs(self) -> dict:
35
+ connection_kwargs = {
36
+ "user": self.environment.get(self.username_environment_key),
37
+ "password": self.environment.get(self.password_environment_key),
38
+ "host": self.environment.get(self.hostname_environment_key),
39
+ "database": self.environment.get(self.database_environment_key),
40
+ "port": self.environment.get(self.port_environment_key, silent=True),
41
+ "sslcert": self.environment.get(self.cert_path_environment_key, silent=True),
42
+ "connect_timeout": self.environment.get(self.connect_timeout_environment_key, silent=True),
43
+ }
44
+
45
+ for kwarg in ["autocommit", "connect_timeout", "port", "sslcert"]:
46
+ if not connection_kwargs[kwarg]:
47
+ del connection_kwargs[kwarg]
48
+
49
+ return {**super().build_connection_kwargs(), **connection_kwargs}
@@ -0,0 +1,35 @@
1
+ import clearskies.configs
2
+ from clearskies import decorators
3
+ from clearskies.cursors.sqlite import Sqlite as SqliteBase
4
+ from clearskies.di import inject
5
+
6
+
7
+ class Sqlite(SqliteBase):
8
+ database_environment_key = clearskies.configs.String(default="DATABASE_NAME")
9
+ autocommit_environment_key = clearskies.configs.String(default="DATABASE_AUTOCOMMIT")
10
+ connect_timeout_environment_key = clearskies.configs.String(default="DATABASE_CONNECT_TIMEOUT")
11
+
12
+ environment = inject.Environment()
13
+
14
+ @decorators.parameters_to_properties
15
+ def __init__(
16
+ self,
17
+ database_environment_key="DATABASE_NAME",
18
+ autocommit_environment_key="DATABASE_AUTOCOMMIT",
19
+ connect_timeout_environment_key="DATABASE_CONNECT_TIMEOUT",
20
+ ):
21
+ pass
22
+
23
+ def build_connection_kwargs(self) -> dict:
24
+ connection_kwargs = {
25
+ "database": self.environment.get(self.database_environment_key),
26
+ "autocommit": self.environment.get(self.autocommit_environment_key, silent=True),
27
+ "connect_timeout": self.environment.get(self.connect_timeout_environment_key, silent=True),
28
+ }
29
+
30
+ for kwarg in ["autocommit", "connect_timeout"]:
31
+ if not connection_kwargs[kwarg]:
32
+ del connection_kwargs[kwarg]
33
+ del connection_kwargs["connect_timeout"]
34
+
35
+ return {**super().build_connection_kwargs(), **connection_kwargs}
@@ -0,0 +1,61 @@
1
+ from types import ModuleType
2
+
3
+ import clearskies.configs
4
+ from clearskies import decorators
5
+ from clearskies.cursors.cursor import Cursor
6
+
7
+
8
+ class Mysql(Cursor):
9
+ hostname = clearskies.configs.String(default="localhost")
10
+ username = clearskies.configs.String(default="root")
11
+ password = clearskies.configs.String(default="")
12
+ port = clearskies.configs.Integer(default=None)
13
+ default_port = clearskies.configs.Integer(default=3306)
14
+ cert_path = clearskies.configs.String(default=None)
15
+
16
+ @decorators.parameters_to_properties
17
+ def __init__(
18
+ self,
19
+ hostname="localhost",
20
+ username="root",
21
+ password="",
22
+ database="example",
23
+ autocommit=True,
24
+ connect_timeout=2,
25
+ port=None,
26
+ cert_path=None,
27
+ port_forwarding=None,
28
+ ):
29
+ pass
30
+
31
+ @property
32
+ def factory(self) -> ModuleType:
33
+ """Return the factory for the cursor."""
34
+ if not hasattr(self, "_factory"):
35
+ try:
36
+ import pymysql
37
+
38
+ self._factory = pymysql
39
+ except ImportError:
40
+ raise ValueError(
41
+ "The cursor requires pymysql to be installed. This is an optional dependency of clearskies, so to include it do a `pip install 'clear-skies[mysql]'`"
42
+ )
43
+ return self._factory
44
+
45
+ def build_connection_kwargs(self) -> dict:
46
+ connection_kwargs = {
47
+ "user": self.username,
48
+ "password": self.password,
49
+ "host": self.hostname,
50
+ "port": self.port,
51
+ "ssl_ca": self.cert_path,
52
+ "autocommit": self.autocommit,
53
+ "cursorclass": self.factory.cursors.DictCursor,
54
+ }
55
+ if not connection_kwargs["ssl_ca"]:
56
+ del connection_kwargs["ssl_ca"]
57
+
58
+ if not connection_kwargs["port"]:
59
+ connection_kwargs["port"] = self.default_port
60
+
61
+ return {**super().build_connection_kwargs(), **connection_kwargs}
@@ -0,0 +1,61 @@
1
+ from types import ModuleType
2
+
3
+ import clearskies.configs
4
+ from clearskies import decorators
5
+ from clearskies.cursors.cursor import Cursor
6
+
7
+
8
+ class Postgresql(Cursor):
9
+ hostname = clearskies.configs.String(default="localhost")
10
+ username = clearskies.configs.String(default="root")
11
+ password = clearskies.configs.String(default="")
12
+ port = clearskies.configs.Integer(default=None)
13
+ default_port = clearskies.configs.Integer(default=5432)
14
+ cert_path = clearskies.configs.String(default=None)
15
+
16
+ @decorators.parameters_to_properties
17
+ def __init__(
18
+ self,
19
+ hostname="localhost",
20
+ username="root",
21
+ password="",
22
+ database="example",
23
+ autocommit=True,
24
+ connect_timeout=2,
25
+ port=None,
26
+ cert_path=None,
27
+ port_forwarding=None,
28
+ ):
29
+ pass
30
+
31
+ @property
32
+ def factory(self) -> ModuleType:
33
+ """Return the factory for the cursor."""
34
+ if not hasattr(self, "_factory"):
35
+ try:
36
+ import psycopg
37
+
38
+ self._factory = psycopg
39
+ except ImportError:
40
+ raise ValueError(
41
+ "The cursor requires psycopg to be installed. This is an optional dependency of clearskies, so to include it do a `pip install 'clear-skies[pgsql]'`"
42
+ )
43
+ return self._factory
44
+
45
+ def build_connection_kwargs(self) -> dict:
46
+ connection_kwargs = {
47
+ "user": self.username,
48
+ "password": self.password,
49
+ "host": self.hostname,
50
+ "port": self.port,
51
+ "ssl_ca": self.cert_path,
52
+ "sslcert": self.cert_path,
53
+ "row_factory": self.factory.rows.dict_row,
54
+ }
55
+ if not connection_kwargs["sslcert"]:
56
+ del connection_kwargs["sslcert"]
57
+
58
+ if not connection_kwargs["port"]:
59
+ connection_kwargs["port"] = self.default_port
60
+
61
+ return {**super().build_connection_kwargs(), **connection_kwargs}
@@ -0,0 +1,62 @@
1
+ from sqlite3 import Cursor as SQLiteCursor
2
+ from types import ModuleType
3
+
4
+ import clearskies.configs
5
+ from clearskies.cursors.cursor import Cursor
6
+ from clearskies.di import inject
7
+
8
+
9
+ class Sqlite(Cursor):
10
+ database = clearskies.configs.String(default="example.db")
11
+ connect_timeout = clearskies.configs.Float(default=2.0) # type: ignore[assignment]
12
+ value_placeholder = "?"
13
+ sys = inject.ByName("sys")
14
+
15
+ def __init__(
16
+ self,
17
+ database="example.db",
18
+ autocommit=True,
19
+ connect_timeout=2.0,
20
+ ):
21
+ self.autocommit = autocommit
22
+ self.database = database
23
+ self.connect_timeout = connect_timeout
24
+
25
+ @property
26
+ def factory(self) -> ModuleType:
27
+ """Return the factory for the cursor."""
28
+ if not hasattr(self, "_factory"):
29
+ try:
30
+ import sqlite3
31
+
32
+ self._factory = sqlite3
33
+ except ImportError as e:
34
+ raise ValueError( # noqa: TRY003
35
+ "The cursor requires sqlite3 to be available. sqlite3 is included with the standard library for Python, "
36
+ f"so this error likely indicates a misconfigured Python installation. Error: {e}"
37
+ ) from e
38
+ return self._factory
39
+
40
+ @property
41
+ def cursor(self) -> SQLiteCursor:
42
+ """Get or create a database cursor instance."""
43
+ if not hasattr(self, "_cursor"):
44
+
45
+ def dict_factory(cursor, row):
46
+ fields = [column[0] for column in cursor.description]
47
+ return dict(zip(fields, row))
48
+
49
+ connection = self.factory.connect(
50
+ database=self.database,
51
+ timeout=2.0,
52
+ )
53
+ connection.row_factory = dict_factory
54
+ if self.autocommit:
55
+ connection.isolation_level = None # Enable autocommit for sqlite3
56
+ else:
57
+ if self.sys.version_info > (3, 12): # noqa: UP036
58
+ connection.autocommit = False
59
+ else:
60
+ connection.isolation_level = "DEFERRED" # Disable autocommit
61
+ self._cursor = connection.cursor()
62
+ return self._cursor # type: ignore[return-value]
@@ -0,0 +1,33 @@
1
+ from __future__ import annotations
2
+
3
+ import inspect
4
+
5
+ import wrapt
6
+
7
+
8
+ @wrapt.decorator
9
+ def parameters_to_properties(wrapped, instance, args, kwargs):
10
+ if not instance:
11
+ raise ValueError(
12
+ "The parameters_to_properties decorator only works for methods in classes, not plain functions"
13
+ )
14
+
15
+ if args:
16
+ wrapped_args = inspect.getfullargspec(wrapped)
17
+ for key, value in zip(wrapped_args.args[1:], args):
18
+ # if it's a dictionary or a list then copy it to avoid linking data
19
+ if isinstance(value, dict):
20
+ value = {**value}
21
+ if isinstance(value, list):
22
+ value = [*value]
23
+ setattr(instance, key, value)
24
+
25
+ for key, value in kwargs.items():
26
+ # if it's a dictionary or a list then copy it to avoid linking data
27
+ if isinstance(value, dict):
28
+ value = {**value}
29
+ if isinstance(value, list):
30
+ value = [*value]
31
+ setattr(instance, key, value)
32
+
33
+ wrapped(*args, **kwargs)
@@ -0,0 +1,10 @@
1
+ # decorators.pyi
2
+ from typing import Any, Callable, TypeVar
3
+
4
+ # A TypeVar is used to say "whatever kind of function comes in,
5
+ # the same kind of function goes out."
6
+ _F = TypeVar("_F", bound=Callable[..., Any])
7
+
8
+ # This is the type signature for your decorator.
9
+ # It tells Pylance that it preserves the signature of the function it wraps.
10
+ def parameters_to_properties(wrapped: _F) -> _F: ...
@@ -0,0 +1,15 @@
1
+ import clearskies.di.inject as inject
2
+ from clearskies.di.additional_config import AdditionalConfig
3
+ from clearskies.di.additional_config_auto_import import AdditionalConfigAutoImport
4
+ from clearskies.di.di import Di
5
+ from clearskies.di.injectable import Injectable
6
+ from clearskies.di.injectable_properties import InjectableProperties
7
+
8
+ __all__ = [
9
+ "AdditionalConfig",
10
+ "AdditionalConfigAutoImport",
11
+ "Di",
12
+ "Injectable",
13
+ "InjectableProperties",
14
+ "inject",
15
+ ]
@@ -0,0 +1,130 @@
1
+ from typing import Any
2
+
3
+
4
+ class AdditionalConfig:
5
+ """
6
+ This class allows you to add additional names to the Di container.
7
+
8
+ The idea here is that you extend the AdditonalConfig class and attach as many
9
+ `provide_*` methods to the class as you want. This allows the developer to declare a number of
10
+ dependencies and easily attach them to the Di container in one go - helpful for modules that
11
+ come with a variety of things that they want to make available to developers.
12
+
13
+ When an AdditionalConfig instance is attached to the Di container, the container (in essence) finds all the
14
+ `provide_*` methods in the class and registers the corresponding name in the Di container - e.g. if you have
15
+ a method called `provide_widget` then when a class or function requests an argument named `widget`
16
+ from the Di container, the container will call the `provide_widget` method on your instance and pass along
17
+ the return value to the thing that requested a `widget`. The `provide_*` methods can declare their own
18
+ dependencies, including ones declared in the same `AdditionalConfig` class (they just can't be circular, of course).
19
+
20
+ As always, keep in mind the priority of dependency injection names (see the `clearskies.di.Di` class for full
21
+ details). If two `AdditionalConfig` objects declare a `provide_*` method with the same name, then the Di
22
+ system will call the method for the `AdditionalConfig` object that was added last.
23
+
24
+ By default the Di system caches all values. If you have a dependency that shouldn't be cached, you can
25
+ extend the `can_cache` method and have it return True/False depending on the name and context.
26
+
27
+ Example:
28
+ ```python
29
+ from clearskies.di import Di, AdditionalConfig
30
+
31
+
32
+ class MyAdditionalConfig(AdditionalConfig):
33
+ def provide_inner_dependency(self):
34
+ return 5
35
+
36
+ def provide_important_thing(self, inner_dependency):
37
+ return inner_dependency * 10
38
+
39
+
40
+ class AnotherAdditionalConfig(AdditionalConfig):
41
+ def provide_inner_dependency(self):
42
+ return 10
43
+
44
+
45
+ di = Di(additional_configs=[MyAdditionalConfig(), AnotherAdditionalConfig()])
46
+ # Equivalent:
47
+ # di = Di()
48
+ # di.add_addtional_configs([MyAdditionalConfig(), AnotherAdditionalConfig()])
49
+
50
+
51
+ def my_function(important_thing):
52
+ print(important_thing) # prints 100
53
+ ```
54
+ """
55
+
56
+ def can_cache(self, name: str, di, context: str) -> bool:
57
+ """
58
+ Cache control.
59
+
60
+ The Di container caches values by default, but this method allows you to override that.
61
+ After fetching an object from the AdditionalConfig class, the Di container will call this method to
62
+ determine whether or not to cache it. `name` will be the name of the Di value that was built, and
63
+ context will be the name of the class that the value was built for. You then return True/False.
64
+
65
+ Note that this controls whether or not to cache the returned value, not whether or not to check the
66
+ cache for a value. The importance is that, once there is a value in the cache, that will be reused
67
+ for all future requests for that name. Example:
68
+
69
+ ```python
70
+ from clearskies.di import Di, AdditionalConfig
71
+ import secrets
72
+
73
+
74
+ class MyAdditionalConfig(AdditionalConfig):
75
+ def provide_random_number_not_cached(self):
76
+ return secrets.randbelow(100)
77
+
78
+ def provide_random_number_cached(self):
79
+ return secrets.randbelow(100)
80
+
81
+ def can_cache(self, name, context=None):
82
+ return name == "random_number_not_cached"
83
+
84
+
85
+ di = Di(additional_configs=MyAdditionalConfig())
86
+
87
+
88
+ def my_function(random_number_cached, random_number_not_cached):
89
+ print(random_number_cached)
90
+ print(random_number_not_cached)
91
+
92
+
93
+ di.call_function(my_function)
94
+ di.call_function(my_function)
95
+ di.call_function(my_function)
96
+ di.call_function(my_function)
97
+
98
+ # This prints something like:
99
+ # 58
100
+ # 12
101
+ # 58
102
+ # 14
103
+ # 58
104
+ # 41
105
+ ```
106
+ """
107
+ return True
108
+
109
+ def can_build(self, name):
110
+ return hasattr(self, f"provide_{name}")
111
+
112
+ def build(self, name, di, context=None):
113
+ if not hasattr(self, f"provide_{name}"):
114
+ raise KeyError(
115
+ f"AdditionalConfig class '{self.__class__.__name__}' cannot build requested dependency, '{name}'"
116
+ )
117
+
118
+ return di.call_function(getattr(self, f"provide_{name}"))
119
+
120
+ def can_build_class(self, class_to_check: type) -> bool:
121
+ """Return True/False to denote if this AdditionalConfig class can provide a given class."""
122
+ return False
123
+
124
+ def build_class(self, class_to_provide: type, argument_name: str, di, context: str = "") -> Any:
125
+ """Return the desired instance of a given class."""
126
+ pass
127
+
128
+ def can_cache_class(self, class_to_build: type, di, context: str) -> bool:
129
+ """Control whether or not the Di container caches the instance after building a class."""
130
+ return False