deev 0.0.1__tar.gz

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.
Files changed (39) hide show
  1. deev-0.0.1/LICENSE +21 -0
  2. deev-0.0.1/PKG-INFO +211 -0
  3. deev-0.0.1/README.md +187 -0
  4. deev-0.0.1/pyproject.toml +37 -0
  5. deev-0.0.1/setup.cfg +4 -0
  6. deev-0.0.1/src/deev/_ImmutableMixin.py +28 -0
  7. deev-0.0.1/src/deev/_MigrationData.py +20 -0
  8. deev-0.0.1/src/deev/__init__.py +30 -0
  9. deev-0.0.1/src/deev/common/ConnectionString.py +108 -0
  10. deev-0.0.1/src/deev/common/DbConnection.py +49 -0
  11. deev-0.0.1/src/deev/common/DbContext.py +12 -0
  12. deev-0.0.1/src/deev/common/DbCursor.py +66 -0
  13. deev-0.0.1/src/deev/common/DbError.py +19 -0
  14. deev-0.0.1/src/deev/common/DbMigrator.py +132 -0
  15. deev-0.0.1/src/deev/common/DbParams.py +9 -0
  16. deev-0.0.1/src/deev/common/DbTableAdapter.py +77 -0
  17. deev-0.0.1/src/deev/common/DbTransactionContext.py +83 -0
  18. deev-0.0.1/src/deev/common/DbTypeMapper.py +17 -0
  19. deev-0.0.1/src/deev/common/__init__.py +27 -0
  20. deev-0.0.1/src/deev/db_migrate.py +116 -0
  21. deev-0.0.1/src/deev/entities.py +284 -0
  22. deev-0.0.1/src/deev/mysql/MysqlTableAdapter.py +290 -0
  23. deev-0.0.1/src/deev/mysql/MysqlTransactionContext.py +169 -0
  24. deev-0.0.1/src/deev/mysql/MysqlTypeMapper.py +74 -0
  25. deev-0.0.1/src/deev/mysql/__init__.py +13 -0
  26. deev-0.0.1/src/deev/py.typed +0 -0
  27. deev-0.0.1/src/deev/sqlite/SqliteTableAdapter.py +290 -0
  28. deev-0.0.1/src/deev/sqlite/SqliteTransactionContext.py +170 -0
  29. deev-0.0.1/src/deev/sqlite/SqliteTypeMapper.py +69 -0
  30. deev-0.0.1/src/deev/sqlite/__init__.py +13 -0
  31. deev-0.0.1/src/deev/translation.py +309 -0
  32. deev-0.0.1/src/deev/utils.py +134 -0
  33. deev-0.0.1/src/deev/validation.py +87 -0
  34. deev-0.0.1/src/deev.egg-info/PKG-INFO +211 -0
  35. deev-0.0.1/src/deev.egg-info/SOURCES.txt +37 -0
  36. deev-0.0.1/src/deev.egg-info/dependency_links.txt +1 -0
  37. deev-0.0.1/src/deev.egg-info/entry_points.txt +2 -0
  38. deev-0.0.1/src/deev.egg-info/requires.txt +11 -0
  39. deev-0.0.1/src/deev.egg-info/top_level.txt +1 -0
deev-0.0.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ © 2023 Shaun Wilson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
deev-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,211 @@
1
+ Metadata-Version: 2.4
2
+ Name: deev
3
+ Version: 0.0.1
4
+ Summary: ..an entity framework for Python.
5
+ Author-email: Shaun Wilson <mrshaunwilson@msn.com>
6
+ License-Expression: MIT
7
+ Keywords: DB,database,entity framework,mapper
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Programming Language :: Python
11
+ Requires-Python: >=3.11
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: hanaro>=1.0.0
15
+ Provides-Extra: dev
16
+ Requires-Dist: coverage; extra == "dev"
17
+ Requires-Dist: mypy; extra == "dev"
18
+ Requires-Dist: punit>=1.4.5; extra == "dev"
19
+ Requires-Dist: py-conventional-semver>=1.0.5; extra == "dev"
20
+ Requires-Dist: twine; extra == "dev"
21
+ Provides-Extra: mysql
22
+ Requires-Dist: mysql-connector-python; extra == "mysql"
23
+ Dynamic: license-file
24
+
25
+
26
+ [![deev on PyPI](https://img.shields.io/pypi/v/deev.svg)](https://pypi.org/project/deev/) [![deev on readthedocs](https://readthedocs.org/projects/deev/badge/?version=latest)](https://deev.readthedocs.io)
27
+
28
+ **deev** (דיב) is an entity framework for Python.
29
+
30
+ This README is only a high-level introduction to **deev**. For more detailed documentation, please view the official docs at [https://deev.readthedocs.io](https://deev.readthedocs.io).
31
+
32
+
33
+ ## Features
34
+
35
+ * Entity-based; perform CRUD operations using Python objects instead of hand-crafting SQL.
36
+ * Validation; Entities validate before they get persisted to a database, also validate entities on-demand.
37
+ * Transaction Contexts; enter and exit transaction scopes with language-level context management, avoid mismanaged transaction states.
38
+ * DB Migrations; use Python code to apply (and undo) schema changes, data translation, etc using `db-migrate` CLI tool for use from CI/CD pipelines.
39
+ * PEP 249 compatible abstractions; no need to refactor code just to switch DBMS.
40
+ * Syntax normalization; parameterize SQL using `%?` instead of provider-specific syntaxes.
41
+ * Raw SQL Access; execute raw SQL as-needed, including provider/DBMS-specific functions (primarily intended for advanced `db-migrate` cases.)
42
+
43
+
44
+ ## Installation
45
+
46
+ You can install `deev` from [PyPI](https://pypi.org/project/deev/) through usual means, such as `pip`:
47
+
48
+ ```bash
49
+ pip install deev
50
+ ```
51
+
52
+
53
+ ## Usage
54
+
55
+ Let's have a look at the two popular use cases: using Python objects for CRUD operations, and using the `db-migrate` CLI tool to manage DB schema.
56
+
57
+ ### Entity CRUD
58
+
59
+ ```python
60
+ # imports
61
+ from deev import entity, field
62
+
63
+ # define a simple entity with an auto-increment PK, an int value column, and a list[str] column
64
+ @entity
65
+ class SimpleEntity:
66
+ id: int = field(autoincrement=True, primary_key=True)
67
+ column1: int
68
+ column2: list[str]
69
+
70
+ # create a database using familiar connection-string syntax
71
+ from deev.utils import create_database
72
+
73
+ connection_str = 'Server=./test_data/;Database=sqlite3/test.db;Provider=sqlite3'
74
+ create_database(connection_str)
75
+
76
+ # connect to your database, create a table for storage, and perform some CRUD operations
77
+ from deev import connect
78
+ from deev.sqlite import SqliteTableAdapter
79
+ with connect(connection_str) as db:
80
+ table = SqliteTableAdapter[SimpleEntity](db)
81
+ table.create_table()
82
+ # CREATE
83
+ entity_key = table.create(SimpleEntity(
84
+ column1=1,
85
+ column2=[3, 2, 1]
86
+ ))
87
+ # READ
88
+ entity = table.read(**entity_key)
89
+ assert entity.id is not None
90
+ assert entity.column1 == 1
91
+ assert entity.column2[0] == 3
92
+ assert entity.column2[1] == 2
93
+ assert entity.column2[2] == 1
94
+ # UPDATE
95
+ entity.column2[1] = 4
96
+ table.update(entity)
97
+ # DELETE
98
+ table.delete(**entity_key)
99
+
100
+ # alternatives: upsert + query
101
+ entity_key = table.upsert(SimpleEntity(
102
+ column1=2,
103
+ column2=[5]
104
+ ))
105
+ entity_key = table.upsert(SimpleEntity(
106
+ column1=2,
107
+ column2=[6]
108
+ ))
109
+ results = table.query(
110
+ where='column1 = %?',
111
+ orderby='column1 DESC',
112
+ limit=2,
113
+ params=(2,)
114
+ )
115
+ count = 0
116
+ for result in results:
117
+ assert result.column2[0] in (5, 6)
118
+ count += 1
119
+ assert count == 2
120
+ # query kwargs are optional, for example this creates a generator for all table records:
121
+ results = table.query()
122
+ ```
123
+
124
+ ### CLI `db-migrate` Tool
125
+
126
+ The `db-migrate` tool can be used to apply a migration script or undo a previously applied migration script.
127
+
128
+ Basic syntax:
129
+
130
+ ```bash
131
+ $ db-migrate -h
132
+ usage: db-migrate [-h] [--verbose] <COMMAND> ...
133
+
134
+ Utility for applying, undoing, or generating migrations.
135
+
136
+ positional arguments:
137
+ <COMMAND> Action to perform.
138
+ apply Apply migrations.
139
+ undo Undo migrations.
140
+
141
+ options:
142
+ -h, --help show this help message and exit
143
+ --verbose Enable verbose logging.
144
+
145
+ $ db-migrate apply -h
146
+ usage: db-migrate apply [-h] [--stop-at name] path connectionstring
147
+
148
+ positional arguments:
149
+ path Directory containing migration scripts.
150
+ connectionstring Database connection string.
151
+
152
+ options:
153
+ -h, --help show this help message and exit
154
+ --stop-at name Stop processing at the named migration.
155
+ ```
156
+
157
+ A migration script is a Python file which defines two functions `apply(...)` and `undo(...)`, each receiving a `DbTransactionContext` you can use to modify the database transactionally. As an example let's assume we modified `SimpleEntity` with an additional attribute `column3` of type `datetime`:
158
+
159
+ ```python
160
+ @entity
161
+ class SimpleEntity:
162
+ id: int = field(autoincrement=True, primary_key=True)
163
+ column1: int
164
+ column2: list[str]
165
+ column3: Optional[datetime]] = field(nullable=True)
166
+ ```
167
+
168
+ Since we already have a table for this entity, we want to modify the schema to support the new attribute:
169
+
170
+ ```python
171
+ # 000_test01.py
172
+ from deev.common import DbTransactionContext
173
+
174
+
175
+ def apply(transaction: DbTransactionContext) -> None:
176
+ # alter the existing entity table
177
+ transaction.execute_nonquery('ALTER TABLE SimpleEntity ADD COLUMN column3 DATETIME')
178
+ transaction.commit()
179
+
180
+
181
+ def undo(transaction: DbTransactionContext) -> None:
182
+ # undo the alteration applied by `apply(...)` above
183
+ transaction.execute_nonquery('ALTER TABLE SimpleEntity DROP COLUMN column3')
184
+ transaction.commit()
185
+ ```
186
+
187
+ Finally, we can apply the change to our existing database:
188
+
189
+ ```bash
190
+ # apply schema change
191
+ db-migrate apply ./test_data/migrations 'Server=./test_data/;Database=sqlite3/test.db;Provider=sqlite3'
192
+ ```
193
+ ```
194
+ ..apply migration "000_test01"
195
+ Migrations applied 1, skipped 0, available 1.
196
+ ```
197
+
198
+ We can also undo the change after it has been applied:
199
+
200
+ ```bash
201
+ # undo schema change
202
+ db-migrate apply ./test_data/migrations 'Server=./test_data/;Database=sqlite3/test.db;Provider=sqlite3'
203
+ ```
204
+ ```
205
+ ..apply migration "000_test01"
206
+ Migrations undone 1, skipped 0, available 1.
207
+ ```
208
+
209
+ ## Contact
210
+
211
+ You can reach me on [Discord](https://discordapp.com/users/307684202080501761) or [open an Issue on Github](https://github.com/wilson0x4d/deev/issues/new/choose).
deev-0.0.1/README.md ADDED
@@ -0,0 +1,187 @@
1
+
2
+ [![deev on PyPI](https://img.shields.io/pypi/v/deev.svg)](https://pypi.org/project/deev/) [![deev on readthedocs](https://readthedocs.org/projects/deev/badge/?version=latest)](https://deev.readthedocs.io)
3
+
4
+ **deev** (דיב) is an entity framework for Python.
5
+
6
+ This README is only a high-level introduction to **deev**. For more detailed documentation, please view the official docs at [https://deev.readthedocs.io](https://deev.readthedocs.io).
7
+
8
+
9
+ ## Features
10
+
11
+ * Entity-based; perform CRUD operations using Python objects instead of hand-crafting SQL.
12
+ * Validation; Entities validate before they get persisted to a database, also validate entities on-demand.
13
+ * Transaction Contexts; enter and exit transaction scopes with language-level context management, avoid mismanaged transaction states.
14
+ * DB Migrations; use Python code to apply (and undo) schema changes, data translation, etc using `db-migrate` CLI tool for use from CI/CD pipelines.
15
+ * PEP 249 compatible abstractions; no need to refactor code just to switch DBMS.
16
+ * Syntax normalization; parameterize SQL using `%?` instead of provider-specific syntaxes.
17
+ * Raw SQL Access; execute raw SQL as-needed, including provider/DBMS-specific functions (primarily intended for advanced `db-migrate` cases.)
18
+
19
+
20
+ ## Installation
21
+
22
+ You can install `deev` from [PyPI](https://pypi.org/project/deev/) through usual means, such as `pip`:
23
+
24
+ ```bash
25
+ pip install deev
26
+ ```
27
+
28
+
29
+ ## Usage
30
+
31
+ Let's have a look at the two popular use cases: using Python objects for CRUD operations, and using the `db-migrate` CLI tool to manage DB schema.
32
+
33
+ ### Entity CRUD
34
+
35
+ ```python
36
+ # imports
37
+ from deev import entity, field
38
+
39
+ # define a simple entity with an auto-increment PK, an int value column, and a list[str] column
40
+ @entity
41
+ class SimpleEntity:
42
+ id: int = field(autoincrement=True, primary_key=True)
43
+ column1: int
44
+ column2: list[str]
45
+
46
+ # create a database using familiar connection-string syntax
47
+ from deev.utils import create_database
48
+
49
+ connection_str = 'Server=./test_data/;Database=sqlite3/test.db;Provider=sqlite3'
50
+ create_database(connection_str)
51
+
52
+ # connect to your database, create a table for storage, and perform some CRUD operations
53
+ from deev import connect
54
+ from deev.sqlite import SqliteTableAdapter
55
+ with connect(connection_str) as db:
56
+ table = SqliteTableAdapter[SimpleEntity](db)
57
+ table.create_table()
58
+ # CREATE
59
+ entity_key = table.create(SimpleEntity(
60
+ column1=1,
61
+ column2=[3, 2, 1]
62
+ ))
63
+ # READ
64
+ entity = table.read(**entity_key)
65
+ assert entity.id is not None
66
+ assert entity.column1 == 1
67
+ assert entity.column2[0] == 3
68
+ assert entity.column2[1] == 2
69
+ assert entity.column2[2] == 1
70
+ # UPDATE
71
+ entity.column2[1] = 4
72
+ table.update(entity)
73
+ # DELETE
74
+ table.delete(**entity_key)
75
+
76
+ # alternatives: upsert + query
77
+ entity_key = table.upsert(SimpleEntity(
78
+ column1=2,
79
+ column2=[5]
80
+ ))
81
+ entity_key = table.upsert(SimpleEntity(
82
+ column1=2,
83
+ column2=[6]
84
+ ))
85
+ results = table.query(
86
+ where='column1 = %?',
87
+ orderby='column1 DESC',
88
+ limit=2,
89
+ params=(2,)
90
+ )
91
+ count = 0
92
+ for result in results:
93
+ assert result.column2[0] in (5, 6)
94
+ count += 1
95
+ assert count == 2
96
+ # query kwargs are optional, for example this creates a generator for all table records:
97
+ results = table.query()
98
+ ```
99
+
100
+ ### CLI `db-migrate` Tool
101
+
102
+ The `db-migrate` tool can be used to apply a migration script or undo a previously applied migration script.
103
+
104
+ Basic syntax:
105
+
106
+ ```bash
107
+ $ db-migrate -h
108
+ usage: db-migrate [-h] [--verbose] <COMMAND> ...
109
+
110
+ Utility for applying, undoing, or generating migrations.
111
+
112
+ positional arguments:
113
+ <COMMAND> Action to perform.
114
+ apply Apply migrations.
115
+ undo Undo migrations.
116
+
117
+ options:
118
+ -h, --help show this help message and exit
119
+ --verbose Enable verbose logging.
120
+
121
+ $ db-migrate apply -h
122
+ usage: db-migrate apply [-h] [--stop-at name] path connectionstring
123
+
124
+ positional arguments:
125
+ path Directory containing migration scripts.
126
+ connectionstring Database connection string.
127
+
128
+ options:
129
+ -h, --help show this help message and exit
130
+ --stop-at name Stop processing at the named migration.
131
+ ```
132
+
133
+ A migration script is a Python file which defines two functions `apply(...)` and `undo(...)`, each receiving a `DbTransactionContext` you can use to modify the database transactionally. As an example let's assume we modified `SimpleEntity` with an additional attribute `column3` of type `datetime`:
134
+
135
+ ```python
136
+ @entity
137
+ class SimpleEntity:
138
+ id: int = field(autoincrement=True, primary_key=True)
139
+ column1: int
140
+ column2: list[str]
141
+ column3: Optional[datetime]] = field(nullable=True)
142
+ ```
143
+
144
+ Since we already have a table for this entity, we want to modify the schema to support the new attribute:
145
+
146
+ ```python
147
+ # 000_test01.py
148
+ from deev.common import DbTransactionContext
149
+
150
+
151
+ def apply(transaction: DbTransactionContext) -> None:
152
+ # alter the existing entity table
153
+ transaction.execute_nonquery('ALTER TABLE SimpleEntity ADD COLUMN column3 DATETIME')
154
+ transaction.commit()
155
+
156
+
157
+ def undo(transaction: DbTransactionContext) -> None:
158
+ # undo the alteration applied by `apply(...)` above
159
+ transaction.execute_nonquery('ALTER TABLE SimpleEntity DROP COLUMN column3')
160
+ transaction.commit()
161
+ ```
162
+
163
+ Finally, we can apply the change to our existing database:
164
+
165
+ ```bash
166
+ # apply schema change
167
+ db-migrate apply ./test_data/migrations 'Server=./test_data/;Database=sqlite3/test.db;Provider=sqlite3'
168
+ ```
169
+ ```
170
+ ..apply migration "000_test01"
171
+ Migrations applied 1, skipped 0, available 1.
172
+ ```
173
+
174
+ We can also undo the change after it has been applied:
175
+
176
+ ```bash
177
+ # undo schema change
178
+ db-migrate apply ./test_data/migrations 'Server=./test_data/;Database=sqlite3/test.db;Provider=sqlite3'
179
+ ```
180
+ ```
181
+ ..apply migration "000_test01"
182
+ Migrations undone 1, skipped 0, available 1.
183
+ ```
184
+
185
+ ## Contact
186
+
187
+ You can reach me on [Discord](https://discordapp.com/users/307684202080501761) or [open an Issue on Github](https://github.com/wilson0x4d/deev/issues/new/choose).
@@ -0,0 +1,37 @@
1
+ [project]
2
+ name = "deev"
3
+ version = "0.0.1"
4
+ description = "..an entity framework for Python."
5
+ keywords = ["DB", "database", "entity framework", "mapper"]
6
+ authors = [
7
+ { name="Shaun Wilson", email="mrshaunwilson@msn.com" }
8
+ ]
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ classifiers = [
12
+ "Intended Audience :: Developers",
13
+ "Operating System :: OS Independent",
14
+ "Programming Language :: Python"
15
+ ]
16
+ requires-python = ">=3.11"
17
+ dependencies = [
18
+ "hanaro (>=1.0.0)"
19
+ ]
20
+
21
+ [project.optional-dependencies]
22
+ dev = [
23
+ "coverage",
24
+ "mypy",
25
+ "punit (>=1.4.5)",
26
+ "py-conventional-semver (>=1.0.5)",
27
+ "twine"
28
+ ]
29
+ mysql = [
30
+ "mysql-connector-python"
31
+ ]
32
+
33
+ [project.scripts]
34
+ db-migrate = "deev.db_migrate:main"
35
+
36
+ [tool.setuptools.package-data]
37
+ "deev" = ["py.typed"]
deev-0.0.1/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,28 @@
1
+ # SPDX-FileCopyrightText: © 2025 Shaun Wilson
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ from typing import Any
5
+
6
+
7
+ class _ImmutableMixin:
8
+ """Mixin that adds a ``freeze`` operation."""
9
+ __frozen__: bool = False
10
+
11
+ def __setattr__(self, name: str, value: Any) -> None:
12
+ if getattr(self, '__frozen__', False):
13
+ raise AttributeError(f'Cannot modify frozen instance: {name}')
14
+ # Call ``super()`` so the next class in the MRO (e.g. BaseModel) can run
15
+ super().__setattr__(name, value)
16
+
17
+ def __delattr__(self, name: str) -> None:
18
+ if getattr(self, '__frozen__', False):
19
+ raise AttributeError(f'Cannot delete attribute from frozen instance: {name}')
20
+ super().__delattr__(name)
21
+
22
+ def __freeze__(self) -> Any:
23
+ """Mark the instance as immutable."""
24
+ object.__setattr__(self, '__frozen__', True)
25
+ return self
26
+
27
+
28
+ __all__ = ['_ImmutableMixin']
@@ -0,0 +1,20 @@
1
+ # SPDX-FileCopyrightText: © 2023 Shaun Wilson
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ from .entities import entity, field
5
+
6
+
7
+ @entity(table_name='_migrationdata')
8
+ class _MigrationData:
9
+ """Internal entity representation of ``_migrationdata`` tables used by ``deev``."""
10
+ id: int = field(
11
+ autoincrement=True,
12
+ primary_key=True
13
+ )
14
+ migration: str = field(
15
+ max=260,
16
+ sqltype='VARCVHAR(260)'
17
+ )
18
+
19
+
20
+ __all__ = ['_MigrationData']
@@ -0,0 +1,30 @@
1
+ # SPDX-FileCopyrightText: © 2023 Shaun Wilson
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ from .common.ConnectionString import ConnectionString
5
+ from .common.DbError import DbError
6
+ from .entities import (
7
+ entity,
8
+ field
9
+ )
10
+ from .utils import connect
11
+ from . import common, entities, mysql, translation, utils, validation
12
+
13
+
14
+ __version__ = '0.0.1'
15
+ __commit__ = 'cb1b8ad'
16
+ __all__ = [
17
+ '__version__', '__commit__',
18
+ 'ConnectionString',
19
+ 'DbError',
20
+ 'common',
21
+ 'connect',
22
+ 'db_migrate',
23
+ 'entities',
24
+ 'entity',
25
+ 'field',
26
+ 'mysql',
27
+ 'translation',
28
+ 'utils',
29
+ 'validation'
30
+ ]
@@ -0,0 +1,108 @@
1
+ # SPDX-FileCopyrightText: © 2023 Shaun Wilson
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ from __future__ import annotations
5
+
6
+ from typing import Optional
7
+
8
+
9
+ class ConnectionString:
10
+ """
11
+ A type-safe "Connection String" representation that can parse/build constituent parts.
12
+ """
13
+
14
+ __server: Optional[str]
15
+ __database: Optional[str]
16
+ __user: Optional[str]
17
+ __password: Optional[str]
18
+ __provider: Optional[str]
19
+
20
+ def __init__(
21
+ self,
22
+ connection_str: Optional[str] = None
23
+ ):
24
+ self.__server = None
25
+ self.__database = None
26
+ self.__user = None
27
+ self.__password = None
28
+ self.__provider = None
29
+ if connection_str is not None:
30
+ self.parse(connection_str)
31
+
32
+ def __str__(self) -> str:
33
+ parts = []
34
+ if self.server is not None:
35
+ parts.append(f'Server={self.server}')
36
+ if self.database is not None:
37
+ parts.append(f'Database={self.database}')
38
+ if self.user is not None:
39
+ parts.append(f'UID={self.user}')
40
+ if self.password is not None:
41
+ parts.append(f'PWD={self.password}')
42
+ if self.provider is not None:
43
+ parts.append(f'Provider={self.provider}')
44
+ return ';'.join(parts)
45
+
46
+ @property
47
+ def server(self) -> Optional[str]:
48
+ return self.__server
49
+
50
+ @server.setter
51
+ def server(self, value: Optional[str]):
52
+ self.__server = value
53
+
54
+ @property
55
+ def database(self) -> Optional[str]:
56
+ return self.__database
57
+
58
+ @database.setter
59
+ def database(self, value: Optional[str]):
60
+ self.__database = value
61
+
62
+ @property
63
+ def user(self) -> Optional[str]:
64
+ return self.__user
65
+
66
+ @user.setter
67
+ def user(self, value: Optional[str]):
68
+ self.__user = value
69
+
70
+ @property
71
+ def password(self) -> Optional[str]:
72
+ return self.__password
73
+
74
+ @password.setter
75
+ def password(self, value: Optional[str]):
76
+ self.__password = value
77
+
78
+ @property
79
+ def provider(self) -> Optional[str]:
80
+ return self.__provider
81
+
82
+ @provider.setter
83
+ def provider(self, value: Optional[str]):
84
+ self.__provider = value
85
+
86
+ def parse(self, connectionstring: Optional[str]) -> ConnectionString:
87
+ parts = (
88
+ []
89
+ if connectionstring is None
90
+ else connectionstring.split(';')
91
+ )
92
+ for part in parts:
93
+ key, value = part.split('=')
94
+ match key.lower():
95
+ case 'server':
96
+ self.server = value
97
+ case 'database':
98
+ self.database = value
99
+ case 'uid' | 'user' | 'user id' | 'username':
100
+ self.user = value
101
+ case 'pwd' | 'password' | 'pass':
102
+ self.password = value
103
+ case 'provider':
104
+ self.provider = value
105
+ return self
106
+
107
+
108
+ __all__ = ['ConnectionString']
@@ -0,0 +1,49 @@
1
+ # SPDX-FileCopyrightText: © 2023 Shaun Wilson
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ from __future__ import annotations
5
+
6
+ from types import TracebackType
7
+ from typing import Any, Literal, Optional, Protocol, runtime_checkable
8
+
9
+ from .DbCursor import DbCursor
10
+
11
+
12
+ @runtime_checkable
13
+ class DbConnection(Protocol):
14
+ """DB-API 2.0 Connection proto."""
15
+
16
+ def cursor(self, *args: Any, **kwargs: Any) -> DbCursor:
17
+ ...
18
+
19
+ def commit(self) -> None:
20
+ ...
21
+
22
+ def rollback(self) -> None:
23
+ ...
24
+
25
+ def close(self) -> None:
26
+ ...
27
+
28
+ def __enter__(self) -> DbConnection:
29
+ ...
30
+
31
+ def __exit__(self, exc_type: Optional[type[BaseException]], exc: Optional[BaseException], tb: Optional[TracebackType], /) -> Literal[False]:
32
+ ...
33
+
34
+ @classmethod
35
+ def __subclasshook__(cls, subclass: type) -> bool | None: # type: ignore[override]
36
+ # if db api providers can't be bothered to follow the
37
+ # spec anyone else can't be bothered to enforce it.
38
+ required = {
39
+ name
40
+ for name in dir(cls)
41
+ if not name.startswith('_')
42
+ }
43
+ for name in required:
44
+ if name not in subclass.__dict__:
45
+ return False # pragma: no cover
46
+ return True
47
+
48
+
49
+ __all__ = ['DbConnection']