clear-skies 1.22.10__py3-none-any.whl → 2.0.23__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.
- clear_skies-2.0.23.dist-info/METADATA +76 -0
- clear_skies-2.0.23.dist-info/RECORD +265 -0
- {clear_skies-1.22.10.dist-info → clear_skies-2.0.23.dist-info}/WHEEL +1 -1
- clearskies/__init__.py +37 -21
- clearskies/action.py +7 -0
- clearskies/authentication/__init__.py +8 -39
- clearskies/authentication/authentication.py +44 -0
- clearskies/authentication/authorization.py +14 -8
- clearskies/authentication/authorization_pass_through.py +14 -10
- clearskies/authentication/jwks.py +135 -58
- clearskies/authentication/public.py +3 -26
- clearskies/authentication/secret_bearer.py +515 -44
- clearskies/autodoc/formats/oai3_json/__init__.py +2 -2
- clearskies/autodoc/formats/oai3_json/oai3_json.py +11 -9
- clearskies/autodoc/formats/oai3_json/parameter.py +6 -3
- clearskies/autodoc/formats/oai3_json/request.py +7 -5
- clearskies/autodoc/formats/oai3_json/response.py +7 -4
- clearskies/autodoc/formats/oai3_json/schema/object.py +10 -1
- clearskies/autodoc/request/__init__.py +2 -0
- clearskies/autodoc/request/header.py +4 -6
- clearskies/autodoc/request/json_body.py +4 -6
- clearskies/autodoc/request/parameter.py +8 -0
- clearskies/autodoc/request/request.py +16 -4
- clearskies/autodoc/request/url_parameter.py +4 -6
- clearskies/autodoc/request/url_path.py +4 -6
- clearskies/autodoc/schema/__init__.py +4 -2
- clearskies/autodoc/schema/array.py +5 -6
- clearskies/autodoc/schema/boolean.py +4 -10
- clearskies/autodoc/schema/date.py +0 -3
- clearskies/autodoc/schema/datetime.py +1 -4
- clearskies/autodoc/schema/double.py +0 -3
- clearskies/autodoc/schema/enum.py +4 -2
- clearskies/autodoc/schema/integer.py +4 -9
- clearskies/autodoc/schema/long.py +0 -3
- clearskies/autodoc/schema/number.py +4 -9
- clearskies/autodoc/schema/object.py +5 -7
- clearskies/autodoc/schema/password.py +0 -3
- clearskies/autodoc/schema/schema.py +11 -0
- clearskies/autodoc/schema/string.py +4 -10
- clearskies/backends/__init__.py +55 -20
- clearskies/backends/api_backend.py +1118 -280
- clearskies/backends/backend.py +54 -85
- clearskies/backends/cursor_backend.py +246 -191
- clearskies/backends/memory_backend.py +514 -208
- clearskies/backends/secrets_backend.py +68 -31
- clearskies/column.py +1221 -0
- clearskies/columns/__init__.py +71 -0
- clearskies/columns/audit.py +306 -0
- clearskies/columns/belongs_to_id.py +478 -0
- clearskies/columns/belongs_to_model.py +129 -0
- clearskies/columns/belongs_to_self.py +109 -0
- clearskies/columns/boolean.py +110 -0
- clearskies/columns/category_tree.py +273 -0
- clearskies/columns/category_tree_ancestors.py +51 -0
- clearskies/columns/category_tree_children.py +126 -0
- clearskies/columns/category_tree_descendants.py +48 -0
- clearskies/columns/created.py +92 -0
- clearskies/columns/created_by_authorization_data.py +114 -0
- clearskies/columns/created_by_header.py +103 -0
- clearskies/columns/created_by_ip.py +90 -0
- clearskies/columns/created_by_routing_data.py +102 -0
- clearskies/columns/created_by_user_agent.py +89 -0
- clearskies/columns/date.py +232 -0
- clearskies/columns/datetime.py +284 -0
- clearskies/columns/email.py +78 -0
- clearskies/columns/float.py +149 -0
- clearskies/columns/has_many.py +529 -0
- clearskies/columns/has_many_self.py +62 -0
- clearskies/columns/has_one.py +21 -0
- clearskies/columns/integer.py +158 -0
- clearskies/columns/json.py +126 -0
- clearskies/columns/many_to_many_ids.py +335 -0
- clearskies/columns/many_to_many_ids_with_data.py +274 -0
- clearskies/columns/many_to_many_models.py +156 -0
- clearskies/columns/many_to_many_pivots.py +132 -0
- clearskies/columns/phone.py +162 -0
- clearskies/columns/select.py +95 -0
- clearskies/columns/string.py +102 -0
- clearskies/columns/timestamp.py +164 -0
- clearskies/columns/updated.py +107 -0
- clearskies/columns/uuid.py +83 -0
- clearskies/configs/README.md +105 -0
- clearskies/configs/__init__.py +170 -0
- clearskies/configs/actions.py +43 -0
- clearskies/configs/any.py +15 -0
- clearskies/configs/any_dict.py +24 -0
- clearskies/configs/any_dict_or_callable.py +25 -0
- clearskies/configs/authentication.py +23 -0
- clearskies/configs/authorization.py +23 -0
- clearskies/configs/boolean.py +18 -0
- clearskies/configs/boolean_or_callable.py +20 -0
- clearskies/configs/callable_config.py +20 -0
- clearskies/configs/columns.py +34 -0
- clearskies/configs/conditions.py +30 -0
- clearskies/configs/config.py +26 -0
- clearskies/configs/datetime.py +20 -0
- clearskies/configs/datetime_or_callable.py +21 -0
- clearskies/configs/email.py +10 -0
- clearskies/configs/email_list.py +17 -0
- clearskies/configs/email_list_or_callable.py +17 -0
- clearskies/configs/email_or_email_list_or_callable.py +59 -0
- clearskies/configs/endpoint.py +23 -0
- clearskies/configs/endpoint_list.py +29 -0
- clearskies/configs/float.py +18 -0
- clearskies/configs/float_or_callable.py +20 -0
- clearskies/configs/headers.py +28 -0
- clearskies/configs/integer.py +18 -0
- clearskies/configs/integer_or_callable.py +20 -0
- clearskies/configs/joins.py +30 -0
- clearskies/configs/list_any_dict.py +32 -0
- clearskies/configs/list_any_dict_or_callable.py +33 -0
- clearskies/configs/model_class.py +35 -0
- clearskies/configs/model_column.py +67 -0
- clearskies/configs/model_columns.py +58 -0
- clearskies/configs/model_destination_name.py +26 -0
- clearskies/configs/model_to_id_column.py +45 -0
- clearskies/configs/readable_model_column.py +11 -0
- clearskies/configs/readable_model_columns.py +11 -0
- clearskies/configs/schema.py +23 -0
- clearskies/configs/searchable_model_columns.py +11 -0
- clearskies/configs/security_headers.py +39 -0
- clearskies/configs/select.py +28 -0
- clearskies/configs/select_list.py +49 -0
- clearskies/configs/string.py +31 -0
- clearskies/configs/string_dict.py +34 -0
- clearskies/configs/string_list.py +47 -0
- clearskies/configs/string_list_or_callable.py +48 -0
- clearskies/configs/string_or_callable.py +18 -0
- clearskies/configs/timedelta.py +20 -0
- clearskies/configs/timezone.py +20 -0
- clearskies/configs/url.py +25 -0
- clearskies/configs/validators.py +45 -0
- clearskies/configs/writeable_model_column.py +11 -0
- clearskies/configs/writeable_model_columns.py +11 -0
- clearskies/configurable.py +78 -0
- clearskies/contexts/__init__.py +8 -8
- clearskies/contexts/cli.py +129 -43
- clearskies/contexts/context.py +93 -56
- clearskies/contexts/wsgi.py +79 -33
- clearskies/contexts/wsgi_ref.py +87 -0
- clearskies/cursors/__init__.py +7 -0
- clearskies/cursors/cursor.py +166 -0
- clearskies/cursors/from_environment/__init__.py +5 -0
- clearskies/cursors/from_environment/mysql.py +51 -0
- clearskies/cursors/from_environment/postgresql.py +49 -0
- clearskies/cursors/from_environment/sqlite.py +35 -0
- clearskies/cursors/mysql.py +61 -0
- clearskies/cursors/postgresql.py +61 -0
- clearskies/cursors/sqlite.py +62 -0
- clearskies/decorators.py +33 -0
- clearskies/decorators.pyi +10 -0
- clearskies/di/__init__.py +11 -7
- clearskies/di/additional_config.py +115 -4
- clearskies/di/additional_config_auto_import.py +12 -0
- clearskies/di/di.py +714 -125
- clearskies/di/inject/__init__.py +23 -0
- clearskies/di/inject/akeyless_sdk.py +16 -0
- clearskies/di/inject/by_class.py +24 -0
- clearskies/di/inject/by_name.py +22 -0
- clearskies/di/inject/di.py +16 -0
- clearskies/di/inject/environment.py +15 -0
- clearskies/di/inject/input_output.py +19 -0
- clearskies/di/inject/now.py +16 -0
- clearskies/di/inject/requests.py +16 -0
- clearskies/di/inject/secrets.py +15 -0
- clearskies/di/inject/utcnow.py +16 -0
- clearskies/di/inject/uuid.py +16 -0
- clearskies/di/injectable.py +32 -0
- clearskies/di/injectable_properties.py +131 -0
- clearskies/end.py +219 -0
- clearskies/endpoint.py +1303 -0
- clearskies/endpoint_group.py +333 -0
- clearskies/endpoints/__init__.py +25 -0
- clearskies/endpoints/advanced_search.py +519 -0
- clearskies/endpoints/callable.py +382 -0
- clearskies/endpoints/create.py +201 -0
- clearskies/endpoints/delete.py +133 -0
- clearskies/endpoints/get.py +267 -0
- clearskies/endpoints/health_check.py +181 -0
- clearskies/endpoints/list.py +567 -0
- clearskies/endpoints/restful_api.py +417 -0
- clearskies/endpoints/schema.py +185 -0
- clearskies/endpoints/simple_search.py +279 -0
- clearskies/endpoints/update.py +188 -0
- clearskies/environment.py +7 -3
- clearskies/exceptions/__init__.py +19 -0
- clearskies/{handlers/exceptions/input_error.py → exceptions/input_errors.py} +1 -1
- clearskies/exceptions/missing_dependency.py +2 -0
- clearskies/exceptions/moved_permanently.py +3 -0
- clearskies/exceptions/moved_temporarily.py +3 -0
- clearskies/functional/__init__.py +2 -2
- clearskies/functional/json.py +47 -0
- clearskies/functional/routing.py +92 -0
- clearskies/functional/string.py +19 -11
- clearskies/functional/validations.py +61 -9
- clearskies/input_outputs/__init__.py +9 -7
- clearskies/input_outputs/cli.py +135 -160
- clearskies/input_outputs/exceptions/__init__.py +6 -1
- clearskies/input_outputs/headers.py +54 -0
- clearskies/input_outputs/input_output.py +77 -123
- clearskies/input_outputs/programmatic.py +62 -0
- clearskies/input_outputs/wsgi.py +36 -48
- clearskies/model.py +1874 -193
- clearskies/query/__init__.py +12 -0
- clearskies/query/condition.py +228 -0
- clearskies/query/join.py +136 -0
- clearskies/query/query.py +193 -0
- clearskies/query/sort.py +27 -0
- clearskies/schema.py +82 -0
- clearskies/secrets/__init__.py +4 -31
- clearskies/secrets/additional_configs/mysql_connection_dynamic_producer.py +15 -4
- clearskies/secrets/additional_configs/mysql_connection_dynamic_producer_via_ssh_cert_bastion.py +11 -5
- clearskies/secrets/akeyless.py +421 -155
- clearskies/secrets/exceptions/__init__.py +7 -1
- clearskies/secrets/exceptions/not_found_error.py +2 -0
- clearskies/secrets/exceptions/permissions_error.py +2 -0
- clearskies/secrets/secrets.py +12 -11
- clearskies/security_header.py +17 -0
- clearskies/security_headers/__init__.py +8 -8
- clearskies/security_headers/cache_control.py +47 -109
- clearskies/security_headers/cors.py +38 -92
- clearskies/security_headers/csp.py +76 -150
- clearskies/security_headers/hsts.py +14 -15
- clearskies/typing.py +11 -0
- clearskies/validator.py +36 -0
- clearskies/validators/__init__.py +33 -0
- clearskies/validators/after_column.py +61 -0
- clearskies/validators/before_column.py +15 -0
- clearskies/validators/in_the_future.py +29 -0
- clearskies/validators/in_the_future_at_least.py +13 -0
- clearskies/validators/in_the_future_at_most.py +12 -0
- clearskies/validators/in_the_past.py +29 -0
- clearskies/validators/in_the_past_at_least.py +12 -0
- clearskies/validators/in_the_past_at_most.py +12 -0
- clearskies/validators/maximum_length.py +25 -0
- clearskies/validators/maximum_value.py +28 -0
- clearskies/validators/minimum_length.py +25 -0
- clearskies/validators/minimum_value.py +28 -0
- clearskies/{input_requirements → validators}/required.py +18 -9
- clearskies/validators/timedelta.py +58 -0
- clearskies/validators/unique.py +28 -0
- clear_skies-1.22.10.dist-info/METADATA +0 -47
- clear_skies-1.22.10.dist-info/RECORD +0 -213
- clearskies/application.py +0 -29
- clearskies/authentication/auth0_jwks.py +0 -118
- clearskies/authentication/auth_exception.py +0 -2
- clearskies/authentication/jwks_jwcrypto.py +0 -51
- clearskies/backends/api_get_only_backend.py +0 -48
- clearskies/backends/example_backend.py +0 -43
- clearskies/backends/file_backend.py +0 -48
- clearskies/backends/json_backend.py +0 -7
- clearskies/backends/restful_api_advanced_search_backend.py +0 -103
- clearskies/binding_config.py +0 -16
- clearskies/column_types/__init__.py +0 -203
- clearskies/column_types/audit.py +0 -249
- clearskies/column_types/belongs_to.py +0 -271
- clearskies/column_types/boolean.py +0 -60
- clearskies/column_types/category_tree.py +0 -304
- clearskies/column_types/column.py +0 -373
- clearskies/column_types/created.py +0 -26
- clearskies/column_types/created_by_authorization_data.py +0 -26
- clearskies/column_types/created_by_header.py +0 -24
- clearskies/column_types/created_by_ip.py +0 -17
- clearskies/column_types/created_by_routing_data.py +0 -25
- clearskies/column_types/created_by_user_agent.py +0 -17
- clearskies/column_types/created_micro.py +0 -26
- clearskies/column_types/datetime.py +0 -109
- clearskies/column_types/datetime_micro.py +0 -13
- clearskies/column_types/email.py +0 -18
- clearskies/column_types/float.py +0 -43
- clearskies/column_types/has_many.py +0 -179
- clearskies/column_types/has_one.py +0 -58
- clearskies/column_types/integer.py +0 -41
- clearskies/column_types/json.py +0 -25
- clearskies/column_types/many_to_many.py +0 -278
- clearskies/column_types/many_to_many_with_data.py +0 -162
- clearskies/column_types/phone.py +0 -48
- clearskies/column_types/select.py +0 -11
- clearskies/column_types/string.py +0 -24
- clearskies/column_types/timestamp.py +0 -73
- clearskies/column_types/updated.py +0 -26
- clearskies/column_types/updated_micro.py +0 -26
- clearskies/column_types/uuid.py +0 -25
- clearskies/columns.py +0 -123
- clearskies/condition_parser.py +0 -172
- clearskies/contexts/build_context.py +0 -54
- clearskies/contexts/convert_to_application.py +0 -190
- clearskies/contexts/extract_handler.py +0 -37
- clearskies/contexts/test.py +0 -94
- clearskies/decorators/__init__.py +0 -39
- clearskies/decorators/auth0_jwks.py +0 -22
- clearskies/decorators/authorization.py +0 -10
- clearskies/decorators/binding_classes.py +0 -9
- clearskies/decorators/binding_modules.py +0 -9
- clearskies/decorators/bindings.py +0 -9
- clearskies/decorators/create.py +0 -10
- clearskies/decorators/delete.py +0 -10
- clearskies/decorators/docs.py +0 -14
- clearskies/decorators/get.py +0 -10
- clearskies/decorators/jwks.py +0 -26
- clearskies/decorators/merge.py +0 -124
- clearskies/decorators/patch.py +0 -10
- clearskies/decorators/post.py +0 -10
- clearskies/decorators/public.py +0 -11
- clearskies/decorators/response_headers.py +0 -10
- clearskies/decorators/return_raw_response.py +0 -9
- clearskies/decorators/schema.py +0 -10
- clearskies/decorators/secret_bearer.py +0 -24
- clearskies/decorators/security_headers.py +0 -10
- clearskies/di/standard_dependencies.py +0 -151
- clearskies/di/test_module/__init__.py +0 -6
- clearskies/di/test_module/another_module/__init__.py +0 -2
- clearskies/di/test_module/module_class.py +0 -5
- clearskies/handlers/__init__.py +0 -41
- clearskies/handlers/advanced_search.py +0 -271
- clearskies/handlers/base.py +0 -479
- clearskies/handlers/callable.py +0 -191
- clearskies/handlers/create.py +0 -35
- clearskies/handlers/crud_by_method.py +0 -18
- clearskies/handlers/database_connector.py +0 -32
- clearskies/handlers/delete.py +0 -61
- clearskies/handlers/exceptions/__init__.py +0 -5
- clearskies/handlers/exceptions/not_found.py +0 -3
- clearskies/handlers/get.py +0 -156
- clearskies/handlers/health_check.py +0 -59
- clearskies/handlers/input_processing.py +0 -79
- clearskies/handlers/list.py +0 -530
- clearskies/handlers/mygrations.py +0 -82
- clearskies/handlers/request_method_routing.py +0 -47
- clearskies/handlers/restful_api.py +0 -218
- clearskies/handlers/routing.py +0 -62
- clearskies/handlers/schema_helper.py +0 -128
- clearskies/handlers/simple_routing.py +0 -206
- clearskies/handlers/simple_routing_route.py +0 -192
- clearskies/handlers/simple_search.py +0 -136
- clearskies/handlers/update.py +0 -96
- clearskies/handlers/write.py +0 -193
- clearskies/input_requirements/__init__.py +0 -78
- clearskies/input_requirements/after.py +0 -36
- clearskies/input_requirements/before.py +0 -36
- clearskies/input_requirements/in_the_future_at_least.py +0 -19
- clearskies/input_requirements/in_the_future_at_most.py +0 -19
- clearskies/input_requirements/in_the_past_at_least.py +0 -19
- clearskies/input_requirements/in_the_past_at_most.py +0 -19
- clearskies/input_requirements/maximum_length.py +0 -19
- clearskies/input_requirements/maximum_value.py +0 -19
- clearskies/input_requirements/minimum_length.py +0 -22
- clearskies/input_requirements/minimum_value.py +0 -19
- clearskies/input_requirements/requirement.py +0 -25
- clearskies/input_requirements/time_delta.py +0 -38
- clearskies/input_requirements/unique.py +0 -18
- clearskies/mocks/__init__.py +0 -7
- clearskies/mocks/input_output.py +0 -124
- clearskies/mocks/models.py +0 -142
- clearskies/models.py +0 -350
- clearskies/security_headers/base.py +0 -12
- clearskies/tests/simple_api/models/__init__.py +0 -2
- clearskies/tests/simple_api/models/status.py +0 -23
- clearskies/tests/simple_api/models/user.py +0 -21
- clearskies/tests/simple_api/users_api.py +0 -64
- {clear_skies-1.22.10.dist-info → clear_skies-2.0.23.dist-info/licenses}/LICENSE +0 -0
- /clearskies/{contexts/bash.py → autodoc/py.typed} +0 -0
- /clearskies/{handlers/exceptions → exceptions}/authentication.py +0 -0
- /clearskies/{handlers/exceptions → exceptions}/authorization.py +0 -0
- /clearskies/{handlers/exceptions → exceptions}/client_error.py +0 -0
- /clearskies/{secrets/exceptions → exceptions}/not_found.py +0 -0
- /clearskies/{tests/__init__.py → input_outputs/py.typed} +0 -0
- /clearskies/{tests/simple_api/__init__.py → py.typed} +0 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
4
|
+
from types import ModuleType
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Callable
|
|
6
|
+
from wsgiref.simple_server import make_server
|
|
7
|
+
|
|
8
|
+
from clearskies.contexts.context import Context
|
|
9
|
+
from clearskies.input_outputs import Wsgi as WsgiInputOutput
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from clearskies.di import AdditionalConfig
|
|
13
|
+
from clearskies.endpoint import Endpoint
|
|
14
|
+
from clearskies.endpoint_group import EndpointGroup
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class WsgiRef(Context):
|
|
18
|
+
"""
|
|
19
|
+
Use a built in WSGI server (for development purposes only).
|
|
20
|
+
|
|
21
|
+
This context will launch a built-in HTTP server for you, so you can run applications locally
|
|
22
|
+
without having to install extra dependencies. Note that this server is not intended for production
|
|
23
|
+
usage, so this is best used for simple tests/demonstration purposes. Unlike the WSGI context, where
|
|
24
|
+
you define the application handler and invoke the context from inside of it (passing along the
|
|
25
|
+
environment and start_response variables), in this case you simply directly invoke the context to
|
|
26
|
+
launch the server. The default port is 8080:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
#!/usr/bin/env python
|
|
30
|
+
import clearskies
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def hello_world(name):
|
|
34
|
+
return f"Hello {name}!"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
wsgi = clearskies.contexts.WsgiRef(
|
|
38
|
+
clearskies.endpoints.Callable(
|
|
39
|
+
hello_world,
|
|
40
|
+
url="/hello/:name",
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
wsgi()
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
And to invoke it:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
curl 'http://localhost:8080/hello/Friend'
|
|
50
|
+
```
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
port: int = 8080
|
|
54
|
+
|
|
55
|
+
def __init__(
|
|
56
|
+
self,
|
|
57
|
+
application: Callable | Endpoint | EndpointGroup,
|
|
58
|
+
port: int = 8080,
|
|
59
|
+
classes: type | list[type] = [],
|
|
60
|
+
modules: ModuleType | list[ModuleType] = [],
|
|
61
|
+
bindings: dict[str, Any] = {},
|
|
62
|
+
additional_configs: AdditionalConfig | list[AdditionalConfig] = [],
|
|
63
|
+
class_overrides: dict[type, type] = {},
|
|
64
|
+
overrides: dict[str, type] = {},
|
|
65
|
+
now: datetime.datetime | None = None,
|
|
66
|
+
utcnow: datetime.datetime | None = None,
|
|
67
|
+
):
|
|
68
|
+
super().__init__(
|
|
69
|
+
application,
|
|
70
|
+
classes=classes,
|
|
71
|
+
modules=modules,
|
|
72
|
+
bindings=bindings,
|
|
73
|
+
additional_configs=additional_configs,
|
|
74
|
+
class_overrides=class_overrides,
|
|
75
|
+
overrides=overrides,
|
|
76
|
+
now=now,
|
|
77
|
+
utcnow=utcnow,
|
|
78
|
+
)
|
|
79
|
+
self.port = port
|
|
80
|
+
|
|
81
|
+
def __call__(self): # type: ignore
|
|
82
|
+
with make_server("", self.port, self.handler) as httpd:
|
|
83
|
+
print(f"Starting WSGI server on port {self.port}. This is NOT intended for production usage.")
|
|
84
|
+
httpd.serve_forever()
|
|
85
|
+
|
|
86
|
+
def handler(self, environment, start_response):
|
|
87
|
+
return self.execute_application(WsgiInputOutput(environment, start_response))
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
from clearskies.cursors.cursor import Cursor
|
|
2
|
+
from clearskies.cursors.mysql import Mysql
|
|
3
|
+
from clearskies.cursors.postgresql import Postgresql
|
|
4
|
+
from clearskies.cursors.sqlite import Sqlite
|
|
5
|
+
from clearskies.cursors import from_environment
|
|
6
|
+
|
|
7
|
+
__all__ = ["Cursor", "Mysql", "Postgresql", "Sqlite", "from_environment"]
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from abc import ABC
|
|
3
|
+
from types import ModuleType
|
|
4
|
+
from typing import Protocol
|
|
5
|
+
|
|
6
|
+
import clearskies.configs
|
|
7
|
+
from clearskies import configurable, decorators
|
|
8
|
+
from clearskies.di import InjectableProperties
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DBAPICursor(Protocol):
|
|
12
|
+
"""
|
|
13
|
+
A minimal protocol for a DB-API 2.0 cursor.
|
|
14
|
+
|
|
15
|
+
This uses structural subtyping. Any object (like a real cursor
|
|
16
|
+
or a mock) will match this type as long as it has the
|
|
17
|
+
required attributes, even if it doesn't explicitly inherit
|
|
18
|
+
from DBAPICursor.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def execute(self, sql: str, parameters: tuple | list):
|
|
22
|
+
"""Execute a SQL statement with parameters."""
|
|
23
|
+
...
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Cursor(ABC, configurable.Configurable, InjectableProperties):
|
|
27
|
+
"""
|
|
28
|
+
Abstract base class for database cursor implementations.
|
|
29
|
+
|
|
30
|
+
Provides a common interface for database operations across different
|
|
31
|
+
database backends. Handles connection management, query execution,
|
|
32
|
+
and SQL formatting with configurable escape characters and placeholders.
|
|
33
|
+
|
|
34
|
+
Attributes
|
|
35
|
+
----------
|
|
36
|
+
database: Name of the database to connect to.
|
|
37
|
+
autocommit: Whether to automatically commit transactions.
|
|
38
|
+
port_forwarding: Optional port forwarding configuration.
|
|
39
|
+
connect_timeout: Connection timeout in seconds.
|
|
40
|
+
table_escape_character: Character used to escape table names.
|
|
41
|
+
column_escape_character: Character used to escape column names.
|
|
42
|
+
value_placeholder: Placeholder character for parameter binding.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
database = clearskies.configs.String(default="example")
|
|
46
|
+
autocommit = clearskies.configs.Boolean(default=True)
|
|
47
|
+
port_forwarding = clearskies.configs.Any(default=None)
|
|
48
|
+
connect_timeout = clearskies.configs.Integer(default=2)
|
|
49
|
+
|
|
50
|
+
table_escape_character = "`"
|
|
51
|
+
column_escape_character = "`"
|
|
52
|
+
value_placeholder = "%s"
|
|
53
|
+
_cursor: DBAPICursor
|
|
54
|
+
_factory: ModuleType
|
|
55
|
+
|
|
56
|
+
@decorators.parameters_to_properties
|
|
57
|
+
def __init__(
|
|
58
|
+
self,
|
|
59
|
+
database="example",
|
|
60
|
+
autocommit=True,
|
|
61
|
+
port_forwarding=None,
|
|
62
|
+
connect_timeout=2,
|
|
63
|
+
):
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def logger(self) -> logging.Logger:
|
|
68
|
+
"""Return the logger for this cursor."""
|
|
69
|
+
return logging.getLogger(self.__class__.__name__)
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def factory(self) -> ModuleType:
|
|
73
|
+
"""Return the factory for the cursor."""
|
|
74
|
+
return self._factory
|
|
75
|
+
|
|
76
|
+
def build_connection_kwargs(self) -> dict:
|
|
77
|
+
"""Return the connection kwargs for the cursor."""
|
|
78
|
+
return {
|
|
79
|
+
"database": self.database,
|
|
80
|
+
"autocommit": self.autocommit,
|
|
81
|
+
"connect_timeout": self.connect_timeout,
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def cursor(self) -> DBAPICursor:
|
|
86
|
+
"""Get or create a database cursor instance."""
|
|
87
|
+
if not hasattr(self, "_cursor"):
|
|
88
|
+
try:
|
|
89
|
+
if self.port_forwarding:
|
|
90
|
+
self.port_forwarding_context()
|
|
91
|
+
except KeyError:
|
|
92
|
+
pass
|
|
93
|
+
|
|
94
|
+
self._cursor = self.factory.connect(
|
|
95
|
+
**self.build_connection_kwargs(),
|
|
96
|
+
).cursor()
|
|
97
|
+
|
|
98
|
+
return self._cursor
|
|
99
|
+
|
|
100
|
+
def __call__(self, *args: configurable.Any, **kwds: configurable.Any) -> configurable.Any:
|
|
101
|
+
return self.cursor
|
|
102
|
+
|
|
103
|
+
def __iter__(self):
|
|
104
|
+
"""Allow direct iteration over the cursor config."""
|
|
105
|
+
return iter(self())
|
|
106
|
+
|
|
107
|
+
def __next__(self):
|
|
108
|
+
"""Allow direct next() calls on the cursor config."""
|
|
109
|
+
return next(self())
|
|
110
|
+
|
|
111
|
+
def port_forwarding_context(self):
|
|
112
|
+
"""Context manager for port forwarding (if applicable)."""
|
|
113
|
+
raise NotImplementedError("Port forwarding not implemented for this cursor.")
|
|
114
|
+
|
|
115
|
+
def column_equals_with_placeholder(self, column_name: str) -> str:
|
|
116
|
+
"""
|
|
117
|
+
Generate SQL for a column equality check with placeholder.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
column_name: Name of the column to check.
|
|
121
|
+
|
|
122
|
+
Returns
|
|
123
|
+
-------
|
|
124
|
+
SQL string in format: `column`=%s
|
|
125
|
+
"""
|
|
126
|
+
return f"{self.column_escape_character}{column_name}{self.column_escape_character}={self.value_placeholder}"
|
|
127
|
+
|
|
128
|
+
def as_sql_with_placeholders(self, table: str, column: str, operator: str, number_values: int = 1) -> str | None:
|
|
129
|
+
"""
|
|
130
|
+
Generate SQL condition with placeholders.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
table: Table name.
|
|
134
|
+
column: Column name.
|
|
135
|
+
operator: SQL operator (e.g., '=', '>', '<').
|
|
136
|
+
number_values: Number of value placeholders needed.
|
|
137
|
+
|
|
138
|
+
Returns
|
|
139
|
+
-------
|
|
140
|
+
SQL string if number_values is 1, None otherwise.
|
|
141
|
+
"""
|
|
142
|
+
if number_values == 1:
|
|
143
|
+
return f"{table}.{column} {operator} {self.value_placeholder}"
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
def execute(self, sql: str, parameters: tuple | list = ()):
|
|
147
|
+
"""
|
|
148
|
+
Execute a SQL statement with parameters.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
sql: SQL statement to execute.
|
|
152
|
+
parameters: Parameters for the SQL statement.
|
|
153
|
+
|
|
154
|
+
Returns
|
|
155
|
+
-------
|
|
156
|
+
Result of cursor.execute().
|
|
157
|
+
"""
|
|
158
|
+
try:
|
|
159
|
+
return self.cursor.execute(sql, parameters)
|
|
160
|
+
except Exception as e:
|
|
161
|
+
self.logger.exception(f"Error executing SQL: {sql} with parameters: {parameters}")
|
|
162
|
+
raise
|
|
163
|
+
|
|
164
|
+
@property
|
|
165
|
+
def lastrowid(self):
|
|
166
|
+
return getattr(self.cursor, "lastrowid", None)
|
|
@@ -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]
|
clearskies/decorators.py
ADDED
|
@@ -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: ...
|
clearskies/di/__init__.py
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
from .di import
|
|
3
|
-
from .
|
|
4
|
-
from .
|
|
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
|
|
5
7
|
|
|
6
8
|
__all__ = [
|
|
7
|
-
"AdditionalConfigAutoImport",
|
|
8
|
-
"DI",
|
|
9
|
-
"StandardDependencies",
|
|
10
9
|
"AdditionalConfig",
|
|
10
|
+
"AdditionalConfigAutoImport",
|
|
11
|
+
"Di",
|
|
12
|
+
"Injectable",
|
|
13
|
+
"InjectableProperties",
|
|
14
|
+
"inject",
|
|
11
15
|
]
|