sqlite-database 0.7.6__tar.gz → 0.7.8__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.
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/.gitignore +1 -0
- {sqlite_database-0.7.6/sqlite_database.egg-info → sqlite_database-0.7.8}/PKG-INFO +24 -1
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/README.md +23 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/TODO.md +5 -5
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/sqlite_database/__init__.py +2 -1
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/sqlite_database/_debug.py +1 -1
- sqlite_database-0.7.8/sqlite_database/asyncs/__init__.py +3 -0
- sqlite_database-0.7.8/sqlite_database/asyncs/database.py +37 -0
- sqlite_database-0.7.8/sqlite_database/asyncs/table.py +95 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/sqlite_database/csv.py +13 -1
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/sqlite_database/database.py +3 -30
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/sqlite_database/models/__init__.py +4 -3
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/sqlite_database/query_builder.py +19 -3
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/sqlite_database/table.py +73 -16
- {sqlite_database-0.7.6 → sqlite_database-0.7.8/sqlite_database.egg-info}/PKG-INFO +24 -1
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/sqlite_database.egg-info/SOURCES.txt +3 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/tests/database/model_api/test_model_api.py +15 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/.editorconfig +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/.github/ISSUE_TEMPLATE/question.md +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/.github/dependabot.yml +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/.github/workflows/pylint.yml +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/.github/workflows/pytest.yml +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/.github/workflows/python-publish.yml +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/.readthedocs.yaml +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/.vscode/settings.json +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/Features.md +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/History.md +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/LICENSE +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/SimpleGuide.md +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/bin/activate +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/bin/check.bat +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/bin/check.sh +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/bin/include/utility.bash +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/bin/install.bash +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/bin/need-installed/activate +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/bin/need-installed/pre-commit +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/bin/summarize-pylint.py +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/dev-config/black.toml +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/dev-config/pylint.toml +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/dev-config/pytest.ini +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/dev-requirements.txt +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/docs/Makefile +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/docs/ModelAPI.md +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/docs/SimpleGuide.md +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/docs/_.md +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/docs/api_reference.rst +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/docs/conf.py +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/docs/index.rst +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/docs/make.bat +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/docs/modules.rst +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/docs/sqlite_database.column.rst +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/docs/sqlite_database.config.rst +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/docs/sqlite_database.csv.rst +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/docs/sqlite_database.database.rst +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/docs/sqlite_database.errors.rst +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/docs/sqlite_database.functions.rst +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/docs/sqlite_database.locals.rst +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/docs/sqlite_database.model.errors.rst +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/docs/sqlite_database.model.helpers.rst +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/docs/sqlite_database.model.query_builder.rst +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/docs/sqlite_database.model.rst +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/docs/sqlite_database.operators.rst +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/docs/sqlite_database.query_builder.rst +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/docs/sqlite_database.rst +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/docs/sqlite_database.signature.rst +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/docs/sqlite_database.subexp.rst +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/docs/sqlite_database.table.rst +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/docs/sqlite_database.typings.rst +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/docs/sqlite_database.utils.rst +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/docs-requirements.txt +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/project-init.bash +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/pyproject.toml +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/setup.cfg +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/setup.py +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/sqlite_database/_utils.py +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/sqlite_database/column.py +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/sqlite_database/errors.py +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/sqlite_database/functions.py +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/sqlite_database/locals.py +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/sqlite_database/models/errors.py +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/sqlite_database/models/helpers.py +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/sqlite_database/models/mixin.py +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/sqlite_database/models/query_builder.py +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/sqlite_database/models/type_checkers.py +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/sqlite_database/operators.py +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/sqlite_database/signature.py +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/sqlite_database/subquery.py +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/sqlite_database/typings.py +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/sqlite_database/utils.py +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/sqlite_database.egg-info/dependency_links.txt +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/sqlite_database.egg-info/requires.txt +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/sqlite_database.egg-info/top_level.txt +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/sqlite_database.egg-info/zip-safe +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/tests/__init__.py +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/tests/database/__init__.py +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/tests/database/model_api/__init__.py +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/tests/database/setup.py +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/tests/database/table_api/__init__.py +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/tests/database/table_api/test_csv.py +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/tests/database/table_api/test_delete.py +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/tests/database/table_api/test_insert.py +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/tests/database/table_api/test_others.py +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/tests/database/table_api/test_select.py +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/tests/database/table_api/test_update.py +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/tests/database/test_custom.py +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/tests/manual_test_performances.py +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/tests/test_internals.py +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/tests/user_benchmark.py +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/tests/user_helpers.py +0 -0
- {sqlite_database-0.7.6 → sqlite_database-0.7.8}/transient/README.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlite_database
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.8
|
|
4
4
|
Summary: A weird wrapper for SQLite Connection
|
|
5
5
|
Home-page: https://github.com/RimuEirnarn/sqlite_database
|
|
6
6
|
Author: RimuEirnarn
|
|
@@ -79,6 +79,29 @@ You can read why this library exists by reading [the history](History.md). The p
|
|
|
79
79
|
|
|
80
80
|
You can submit any issue if you found a good issue. You can submit a pull request as long as the thing you want complies with what this project aims for.
|
|
81
81
|
|
|
82
|
+
## Development
|
|
83
|
+
|
|
84
|
+
When pulling this repository with latest commit, make sure to install all `dev-requirements.txt` depending on your intentions. It's mostly pylint and pytest.
|
|
85
|
+
|
|
86
|
+
When using `pytest`, do this: `pytest --config-file=./dev-config/pytest.ini` or check any check scripts in `bin/`
|
|
87
|
+
|
|
88
|
+
### How to install?
|
|
89
|
+
|
|
90
|
+
```sh
|
|
91
|
+
git clone https://github.com/RimuEirnarn/sqlite_database
|
|
92
|
+
cd sqlite_database
|
|
93
|
+
|
|
94
|
+
python -m venv .venv
|
|
95
|
+
.venv/bin/activate
|
|
96
|
+
|
|
97
|
+
pip install -r ./dev-requirements.txt
|
|
98
|
+
./bin/check.sh
|
|
99
|
+
|
|
100
|
+
# Above script is equivalent to:
|
|
101
|
+
pylint --rcfile ./dev-config/pylint.toml sqlite_database
|
|
102
|
+
pytest --config-file ./dev-config/pytest.ini
|
|
103
|
+
```
|
|
104
|
+
|
|
82
105
|
## License
|
|
83
106
|
|
|
84
107
|
This library/wrapper/repo/project is licensed with/in BSD 3-Clause "New" or "Revised" License.
|
|
@@ -53,6 +53,29 @@ You can read why this library exists by reading [the history](History.md). The p
|
|
|
53
53
|
|
|
54
54
|
You can submit any issue if you found a good issue. You can submit a pull request as long as the thing you want complies with what this project aims for.
|
|
55
55
|
|
|
56
|
+
## Development
|
|
57
|
+
|
|
58
|
+
When pulling this repository with latest commit, make sure to install all `dev-requirements.txt` depending on your intentions. It's mostly pylint and pytest.
|
|
59
|
+
|
|
60
|
+
When using `pytest`, do this: `pytest --config-file=./dev-config/pytest.ini` or check any check scripts in `bin/`
|
|
61
|
+
|
|
62
|
+
### How to install?
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
git clone https://github.com/RimuEirnarn/sqlite_database
|
|
66
|
+
cd sqlite_database
|
|
67
|
+
|
|
68
|
+
python -m venv .venv
|
|
69
|
+
.venv/bin/activate
|
|
70
|
+
|
|
71
|
+
pip install -r ./dev-requirements.txt
|
|
72
|
+
./bin/check.sh
|
|
73
|
+
|
|
74
|
+
# Above script is equivalent to:
|
|
75
|
+
pylint --rcfile ./dev-config/pylint.toml sqlite_database
|
|
76
|
+
pytest --config-file ./dev-config/pytest.ini
|
|
77
|
+
```
|
|
78
|
+
|
|
56
79
|
## License
|
|
57
80
|
|
|
58
81
|
This library/wrapper/repo/project is licensed with/in BSD 3-Clause "New" or "Revised" License.
|
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
What API should we bring? These todo's is for simplification; you can do anything with `Database.sql` property
|
|
10
10
|
|
|
11
11
|
- [x] sqlite function support
|
|
12
|
-
- [
|
|
13
|
-
- [
|
|
14
|
-
- [
|
|
12
|
+
- [x] sqlite aggregate function support
|
|
13
|
+
- [x] sqlite window function support
|
|
14
|
+
- [x] sqlite collation support
|
|
15
15
|
- [x] sqlite pragma
|
|
16
|
-
- [
|
|
16
|
+
- [x] sqlite branching subquery (??)
|
|
17
17
|
- [ ] sqlite 'as' keyword (??)
|
|
18
18
|
- [x] sqlite select data crunching[^1] by using Database config option (crunch=True)
|
|
19
19
|
- [x] sqlite select 'only' data. Instead of using `select *`, we should also have `only`. Bring up few data than select everything.[^2]
|
|
@@ -25,7 +25,7 @@ The functionality here is outside of sqlite features such as export and import.
|
|
|
25
25
|
|
|
26
26
|
- [ ] YAML/JSON/TOML/CUSTOM scheme/table include
|
|
27
27
|
- [x] CSV Export
|
|
28
|
-
- [
|
|
28
|
+
- [x] CSV Import
|
|
29
29
|
|
|
30
30
|
1. abc
|
|
31
31
|
- a
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Database"""
|
|
2
2
|
|
|
3
|
+
from . import models
|
|
3
4
|
from .models import BaseModel, model, Foreign, Primary, Unique, hook, validate
|
|
4
5
|
from .database import Database
|
|
5
6
|
from ._utils import Row, Null
|
|
@@ -14,7 +15,7 @@ def test_installed():
|
|
|
14
15
|
return True
|
|
15
16
|
|
|
16
17
|
|
|
17
|
-
__version__ = "0.7.
|
|
18
|
+
__version__ = "0.7.8"
|
|
18
19
|
__all__ = [
|
|
19
20
|
"Database",
|
|
20
21
|
"Table",
|
|
@@ -39,7 +39,7 @@ def if_debug_print(*args, sep=" ", end="\n", flush=True):
|
|
|
39
39
|
arg0 = args[0]
|
|
40
40
|
print(
|
|
41
41
|
arg0,
|
|
42
|
-
*(repr(arg) for arg in args if args.index(arg) != 0),
|
|
42
|
+
*(repr(arg) if arg != "\n" else "\n" for arg in args if args.index(arg) != 0),
|
|
43
43
|
sep=sep,
|
|
44
44
|
end=end,
|
|
45
45
|
flush=flush,
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""AsyncDatabase"""
|
|
2
|
+
|
|
3
|
+
from sqlite3 import connect, Connection
|
|
4
|
+
from threading import local
|
|
5
|
+
|
|
6
|
+
from .table import AsyncTable
|
|
7
|
+
from ..database import Database
|
|
8
|
+
from .._utils import dict_factory
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AsyncDatabase(Database):
|
|
12
|
+
"""Async (threads, subprocess) ready"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, path: str, **kwargs) -> None:
|
|
15
|
+
self._local = local()
|
|
16
|
+
self._table_class = AsyncTable
|
|
17
|
+
super().__init__(path, **kwargs)
|
|
18
|
+
|
|
19
|
+
def _create_connection(self):
|
|
20
|
+
conn = getattr(self._local, "conn", None)
|
|
21
|
+
if conn is None:
|
|
22
|
+
timeout = self._kwargs.pop('timeout', 30)
|
|
23
|
+
conn = connect(
|
|
24
|
+
self._path,
|
|
25
|
+
timeout=timeout,
|
|
26
|
+
isolation_level=self._kwargs.pop("isolation_level", None),
|
|
27
|
+
check_same_thread=self._kwargs.pop("check_same_thread", False)
|
|
28
|
+
)
|
|
29
|
+
conn.row_factory = dict_factory
|
|
30
|
+
conn.execute("PRAGMA journal_mode=WAL;")
|
|
31
|
+
if isinstance(timeout, int):
|
|
32
|
+
conn.execute(f'PRAGMA busy_timeout={timeout * 1000};')
|
|
33
|
+
self._local.conn = conn
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def _database(self) -> Connection:
|
|
37
|
+
return self._local.conn
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""Async Table"""
|
|
2
|
+
|
|
3
|
+
# pylint: disable=invalid-overridden-method
|
|
4
|
+
import asyncio
|
|
5
|
+
from contextvars import ContextVar
|
|
6
|
+
|
|
7
|
+
from ..table import Table
|
|
8
|
+
from ..query_builder import Condition
|
|
9
|
+
from ..typings import Orders
|
|
10
|
+
|
|
11
|
+
# Context-local transaction depth stack per async task
|
|
12
|
+
_tx_stack = ContextVar("_tx_stack", default=[])
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AsyncTable(Table):
|
|
16
|
+
"""Async (threading/multiprocess ready) Table"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, parent, table: str, columns=None, aggressive_select=False):
|
|
19
|
+
super().__init__(parent, table, columns, False)
|
|
20
|
+
if (self._columns is None and aggressive_select) and table != "sqlite_master":
|
|
21
|
+
asyncio.get_event_loop().run_until_complete(
|
|
22
|
+
asyncio.to_thread(self._fetch_columns)
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
async def _begin_transaction(self):
|
|
26
|
+
"""Start a transaction or savepoint depending on depth."""
|
|
27
|
+
stack = list(_tx_stack.get()) # copy since ContextVar values are immutable
|
|
28
|
+
depth = len(stack)
|
|
29
|
+
|
|
30
|
+
if depth == 0:
|
|
31
|
+
await asyncio.to_thread(self._sql.execute, "BEGIN TRANSACTION")
|
|
32
|
+
else:
|
|
33
|
+
savepoint_name = f"sp_{depth}"
|
|
34
|
+
await asyncio.to_thread(self._sql.execute, f"SAVEPOINT {savepoint_name}")
|
|
35
|
+
|
|
36
|
+
stack.append(True)
|
|
37
|
+
_tx_stack.set(stack)
|
|
38
|
+
|
|
39
|
+
async def _commit_transaction(self):
|
|
40
|
+
"""Commit or release savepoint depending on depth."""
|
|
41
|
+
stack = list(_tx_stack.get())
|
|
42
|
+
depth = len(stack)
|
|
43
|
+
|
|
44
|
+
if depth == 1:
|
|
45
|
+
await asyncio.to_thread(self._sql.commit)
|
|
46
|
+
elif depth > 1:
|
|
47
|
+
savepoint_name = f"sp_{depth-1}"
|
|
48
|
+
await asyncio.to_thread(
|
|
49
|
+
self._sql.execute, f"RELEASE SAVEPOINT {savepoint_name}"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
stack.pop()
|
|
53
|
+
_tx_stack.set(stack)
|
|
54
|
+
|
|
55
|
+
async def _rollback_transaction(self):
|
|
56
|
+
"""Rollback or rollback to savepoint depending on depth."""
|
|
57
|
+
stack = list(_tx_stack.get())
|
|
58
|
+
depth = len(stack)
|
|
59
|
+
|
|
60
|
+
if depth == 1:
|
|
61
|
+
await asyncio.to_thread(self._sql.rollback)
|
|
62
|
+
elif depth > 1:
|
|
63
|
+
savepoint_name = f"sp_{depth-1}"
|
|
64
|
+
await asyncio.to_thread(
|
|
65
|
+
self._sql.execute, f"ROLLBACK TO SAVEPOINT {savepoint_name}"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
stack.pop()
|
|
69
|
+
_tx_stack.set(stack)
|
|
70
|
+
|
|
71
|
+
# ✅ async context manager compatible with your original Table
|
|
72
|
+
async def __aenter__(self):
|
|
73
|
+
await self._begin_transaction()
|
|
74
|
+
return self
|
|
75
|
+
|
|
76
|
+
async def __aexit__(self, exc_type, *_):
|
|
77
|
+
if exc_type:
|
|
78
|
+
await self._rollback_transaction()
|
|
79
|
+
else:
|
|
80
|
+
await self._commit_transaction()
|
|
81
|
+
|
|
82
|
+
async def begin(self):
|
|
83
|
+
"""Begin"""
|
|
84
|
+
await self._begin_transaction()
|
|
85
|
+
|
|
86
|
+
async def commit(self):
|
|
87
|
+
await self._commit_transaction()
|
|
88
|
+
|
|
89
|
+
async def rollback(self):
|
|
90
|
+
await self._rollback_transaction()
|
|
91
|
+
|
|
92
|
+
async def delete(
|
|
93
|
+
self, where: Condition = None, limit: int = 0, order: Orders | None = None
|
|
94
|
+
):
|
|
95
|
+
await asyncio.to_thread(self.delete, where, limit, order)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""CSV module, used to export database/table to csv"""
|
|
2
2
|
|
|
3
|
-
from csv import DictWriter
|
|
3
|
+
from csv import DictWriter, DictReader
|
|
4
4
|
from io import StringIO
|
|
5
5
|
from os import mkdir
|
|
6
6
|
from os.path import join as join_path, isfile, exists
|
|
@@ -65,3 +65,15 @@ def to_csv_file(table_or_database: Table | Database, file: str):
|
|
|
65
65
|
with open(file, "w", encoding="utf-8") as fio:
|
|
66
66
|
fio.write(readed)
|
|
67
67
|
return True
|
|
68
|
+
|
|
69
|
+
def from_csv_string(table: Table, data: str):
|
|
70
|
+
"""Insert from CSV data"""
|
|
71
|
+
reader = DictReader(data)
|
|
72
|
+
for entry in reader:
|
|
73
|
+
table.insert(entry)
|
|
74
|
+
|
|
75
|
+
def from_csv_file(table: Table, data: str):
|
|
76
|
+
"""Insert from CSV file"""
|
|
77
|
+
with open(data, encoding='utf-8') as f:
|
|
78
|
+
fdata = f.read()
|
|
79
|
+
return from_csv_string(table, fdata)
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
"""SQLite Database"""
|
|
2
2
|
|
|
3
3
|
from atexit import register as finalize
|
|
4
|
-
from sqlite3 import OperationalError, connect
|
|
5
|
-
from threading import local
|
|
4
|
+
from sqlite3 import OperationalError, connect
|
|
6
5
|
from typing import Iterable, Literal, Optional
|
|
7
6
|
|
|
8
7
|
from sqlite_database._debug import if_debug_print
|
|
@@ -46,6 +45,7 @@ class Database: # pylint: disable=too-many-instance-attributes
|
|
|
46
45
|
del kwargs['strict']
|
|
47
46
|
self._config = None
|
|
48
47
|
self._closed = False
|
|
48
|
+
self._table_class = Table
|
|
49
49
|
if not self._closed or self.__dict__.get("_initiated", False) is False:
|
|
50
50
|
finalize(self._finalizer)
|
|
51
51
|
self._initiated = True
|
|
@@ -119,7 +119,7 @@ class Database: # pylint: disable=too-many-instance-attributes
|
|
|
119
119
|
raise DatabaseMissingError(f"table {table} does not exists.")
|
|
120
120
|
|
|
121
121
|
try:
|
|
122
|
-
this_table =
|
|
122
|
+
this_table = self._table_class(self, table, __columns)
|
|
123
123
|
except OperationalError as exc:
|
|
124
124
|
dberror = DatabaseMissingError(f"table {table} does not exists")
|
|
125
125
|
dberror.add_note(f"{type(exc).__name__}: {exc!s}")
|
|
@@ -228,30 +228,3 @@ class Database: # pylint: disable=too-many-instance-attributes
|
|
|
228
228
|
def sql(self):
|
|
229
229
|
"""SQL Connection"""
|
|
230
230
|
return self._database
|
|
231
|
-
|
|
232
|
-
class AsyncDatabase(Database):
|
|
233
|
-
"""Async (threads, subprocess) ready"""
|
|
234
|
-
|
|
235
|
-
def __init__(self, path: str, **kwargs) -> None:
|
|
236
|
-
super().__init__(path, **kwargs)
|
|
237
|
-
self._local = local()
|
|
238
|
-
|
|
239
|
-
def _create_connection(self):
|
|
240
|
-
conn = getattr(self._local, "conn", None)
|
|
241
|
-
if conn is None:
|
|
242
|
-
timeout = self._kwargs.pop('timeout', 30)
|
|
243
|
-
conn = connect(
|
|
244
|
-
self._path,
|
|
245
|
-
timeout=timeout,
|
|
246
|
-
isolation_level=self._kwargs.pop("isolation_level", None),
|
|
247
|
-
check_same_thread=self._kwargs.pop("check_same_thread", False)
|
|
248
|
-
)
|
|
249
|
-
conn.row_factory = dict_factory
|
|
250
|
-
conn.execute("PRAGMA journal_mode=WAL;")
|
|
251
|
-
if isinstance(timeout, int):
|
|
252
|
-
conn.execute(f'PRAGMA busy_timeout={timeout * 1000};')
|
|
253
|
-
self._local.conn = conn
|
|
254
|
-
|
|
255
|
-
@property
|
|
256
|
-
def _database(self) -> Connection:
|
|
257
|
-
return self._local.conn
|
|
@@ -155,7 +155,7 @@ class BaseModel: # pylint: disable=too-few-public-methods,too-many-public-metho
|
|
|
155
155
|
def create(cls, **kwargs):
|
|
156
156
|
"""Create data based on kwargs"""
|
|
157
157
|
primary: str | None = cls._primary or kwargs.get("id", None)
|
|
158
|
-
id_present = bool(kwargs.get("id", None))
|
|
158
|
+
id_present = bool(kwargs.get(cls._primary or "id", None))
|
|
159
159
|
if primary and cls.__auto_id__ and not id_present: # type: ignore
|
|
160
160
|
kwargs[primary] = cls.__auto_id__() # type: ignore
|
|
161
161
|
instance = cls(**kwargs)
|
|
@@ -363,9 +363,10 @@ class BaseModel: # pylint: disable=too-few-public-methods,too-many-public-metho
|
|
|
363
363
|
f"with {self.__class__.__name__}"
|
|
364
364
|
)
|
|
365
365
|
|
|
366
|
-
|
|
366
|
+
@classmethod
|
|
367
|
+
def get_table(cls):
|
|
367
368
|
"""Return table instance"""
|
|
368
|
-
return
|
|
369
|
+
return cls._tbl
|
|
369
370
|
|
|
370
371
|
@classmethod
|
|
371
372
|
def where(cls, **kwargs):
|
|
@@ -26,6 +26,8 @@ DEFAULT_MAPPINGS = {value: value for value in _SQLITETYPES}
|
|
|
26
26
|
SQL_ACTIONS = {"null": "set null"}
|
|
27
27
|
MAX_SUBQUERY_STACK_LIMIT = 10
|
|
28
28
|
|
|
29
|
+
NAMING_FORMAT = "{key}{suffix}__{call_id}_{depth}_{condition_id}"
|
|
30
|
+
|
|
29
31
|
|
|
30
32
|
def set_subquery_stack_limit(value: int):
|
|
31
33
|
"""Set subquery stack limit"""
|
|
@@ -200,7 +202,7 @@ def _handle_like(key, middle, val):
|
|
|
200
202
|
return clause
|
|
201
203
|
|
|
202
204
|
|
|
203
|
-
def extract_signature(
|
|
205
|
+
def extract_signature( # pylint: disable=too-many-locals
|
|
204
206
|
filter_: Condition | CacheCond = None, suffix: str = "_check", depth: int = 0
|
|
205
207
|
):
|
|
206
208
|
"""Extract filter signature."""
|
|
@@ -227,7 +229,13 @@ def extract_signature( # pylint: disable=too-many-locals
|
|
|
227
229
|
|
|
228
230
|
val = (
|
|
229
231
|
Signature(
|
|
230
|
-
|
|
232
|
+
":"+NAMING_FORMAT.format(
|
|
233
|
+
key=key,
|
|
234
|
+
suffix=suffix,
|
|
235
|
+
call_id=call_id,
|
|
236
|
+
depth=depth,
|
|
237
|
+
condition_id=condition_id,
|
|
238
|
+
),
|
|
231
239
|
value.generate(),
|
|
232
240
|
value.data,
|
|
233
241
|
)
|
|
@@ -257,7 +265,15 @@ def extract_signature( # pylint: disable=too-many-locals
|
|
|
257
265
|
clauses.append(clause)
|
|
258
266
|
|
|
259
267
|
if val.value is not null:
|
|
260
|
-
data[
|
|
268
|
+
data[
|
|
269
|
+
NAMING_FORMAT.format(
|
|
270
|
+
key=key,
|
|
271
|
+
suffix=suffix,
|
|
272
|
+
call_id=call_id,
|
|
273
|
+
depth=depth,
|
|
274
|
+
condition_id=condition_id,
|
|
275
|
+
)
|
|
276
|
+
] = old_data
|
|
261
277
|
|
|
262
278
|
if not clauses:
|
|
263
279
|
return "", data
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"""Table"""
|
|
2
2
|
|
|
3
|
-
# pylint: disable=too-many-arguments,too-many-public-methods
|
|
3
|
+
# pylint: disable=too-many-arguments,too-many-public-methods,R0801
|
|
4
4
|
|
|
5
|
-
from
|
|
5
|
+
from contextvars import ContextVar
|
|
6
|
+
from sqlite3 import Connection, Error, OperationalError
|
|
6
7
|
from typing import (
|
|
7
8
|
Any,
|
|
8
9
|
Generator,
|
|
@@ -44,6 +45,7 @@ from .typings import (
|
|
|
44
45
|
|
|
45
46
|
# Let's add a little bit of 'black' magic here.
|
|
46
47
|
_null = Function("__NULL__")()
|
|
48
|
+
_tx_stack = ContextVar("_tx_stack", default=[])
|
|
47
49
|
|
|
48
50
|
class Table: # pylint: disable=too-many-instance-attributes
|
|
49
51
|
"""Table. Make sure you remember how the table goes."""
|
|
@@ -74,20 +76,40 @@ class Table: # pylint: disable=too-many-instance-attributes
|
|
|
74
76
|
if (self._columns is None and aggresive_select) and table != "sqlite_master":
|
|
75
77
|
self._fetch_columns()
|
|
76
78
|
|
|
79
|
+
# def __enter__(self):
|
|
80
|
+
# self._prev_auto = self._auto
|
|
81
|
+
# self._prev_autocommit = self._sql.isolation_level
|
|
82
|
+
|
|
83
|
+
# self._sql.isolation_level = None
|
|
84
|
+
# self._auto = False
|
|
85
|
+
# self._sql.execute("BEGIN TRANSACTION")
|
|
86
|
+
# return self
|
|
87
|
+
|
|
88
|
+
# def __exit__(self, exc_type, _, __):
|
|
89
|
+
# if exc_type is None:
|
|
90
|
+
# self._sql.commit()
|
|
91
|
+
# else:
|
|
92
|
+
# self._sql.rollback()
|
|
93
|
+
# self._sql.isolation_level = self._prev_autocommit
|
|
94
|
+
# self._auto = self._prev_auto
|
|
95
|
+
|
|
77
96
|
def __enter__(self):
|
|
78
97
|
self._prev_auto = self._auto
|
|
79
98
|
self._prev_autocommit = self._sql.isolation_level
|
|
80
99
|
|
|
81
|
-
self._sql.isolation_level = None
|
|
82
100
|
self._auto = False
|
|
83
|
-
self._sql.
|
|
101
|
+
self._sql.isolation_level = None
|
|
102
|
+
self._begin_transaction()
|
|
84
103
|
return self
|
|
85
104
|
|
|
86
105
|
def __exit__(self, exc_type, _, __):
|
|
87
106
|
if exc_type is None:
|
|
88
|
-
self.
|
|
107
|
+
self._commit_transaction()
|
|
108
|
+
self._dirty = False
|
|
89
109
|
else:
|
|
90
|
-
self.
|
|
110
|
+
self._rollback_transaction()
|
|
111
|
+
self._dirty = False
|
|
112
|
+
|
|
91
113
|
self._sql.isolation_level = self._prev_autocommit
|
|
92
114
|
self._auto = self._prev_auto
|
|
93
115
|
|
|
@@ -167,10 +189,11 @@ class Table: # pylint: disable=too-many-instance-attributes
|
|
|
167
189
|
fn = cursor.execute if which == "execute" else cursor.executemany
|
|
168
190
|
try:
|
|
169
191
|
fn(query, data)
|
|
170
|
-
except
|
|
192
|
+
except Error as exc:
|
|
171
193
|
if str(exc).startswith("no such table:"):
|
|
172
194
|
raise TableRemovedError(f"Table {self._table} doesn't exists anymore") from None
|
|
173
195
|
exc.add_note(f"SQL query: {query}")
|
|
196
|
+
exc.add_note(f"Arguments: {data}")
|
|
174
197
|
exc.add_note(
|
|
175
198
|
f"There's about {1 if isinstance(data, dict) else len(data)} value(s) inserted"
|
|
176
199
|
)
|
|
@@ -587,12 +610,55 @@ constraint is enabled."
|
|
|
587
610
|
def commit(self):
|
|
588
611
|
"""Commit changes"""
|
|
589
612
|
self._sql.commit()
|
|
613
|
+
self._dirty = False
|
|
590
614
|
|
|
591
615
|
def rollback(self):
|
|
592
616
|
"""Rollback"""
|
|
593
617
|
self._sql.rollback()
|
|
594
618
|
self._dirty = False
|
|
595
619
|
|
|
620
|
+
def _begin_transaction(self):
|
|
621
|
+
"""Start a transaction or savepoint depending on depth."""
|
|
622
|
+
stack = list(_tx_stack.get()) # copy since ContextVar values are immutable
|
|
623
|
+
depth = len(stack)
|
|
624
|
+
|
|
625
|
+
if depth == 0:
|
|
626
|
+
self._sql.execute("BEGIN TRANSACTION")
|
|
627
|
+
else:
|
|
628
|
+
savepoint_name = f"sp_{depth}"
|
|
629
|
+
self._sql.execute(f"SAVEPOINT {savepoint_name}")
|
|
630
|
+
|
|
631
|
+
stack.append(True)
|
|
632
|
+
_tx_stack.set(stack)
|
|
633
|
+
|
|
634
|
+
def _commit_transaction(self):
|
|
635
|
+
"""Commit or release savepoint depending on depth."""
|
|
636
|
+
stack = list(_tx_stack.get())
|
|
637
|
+
depth = len(stack)
|
|
638
|
+
|
|
639
|
+
if depth == 1:
|
|
640
|
+
self._sql.commit()
|
|
641
|
+
elif depth > 1:
|
|
642
|
+
savepoint_name = f"sp_{depth-1}"
|
|
643
|
+
self._sql.execute(f"RELEASE SAVEPOINT {savepoint_name}")
|
|
644
|
+
stack.pop()
|
|
645
|
+
_tx_stack.set(stack)
|
|
646
|
+
|
|
647
|
+
def _rollback_transaction(self):
|
|
648
|
+
"""Rollback or rollback to savepoint depending on depth."""
|
|
649
|
+
stack = list(_tx_stack.get())
|
|
650
|
+
depth = len(stack)
|
|
651
|
+
|
|
652
|
+
if depth == 1:
|
|
653
|
+
self._sql.rollback()
|
|
654
|
+
elif depth > 1:
|
|
655
|
+
savepoint_name = f"sp_{depth-1}"
|
|
656
|
+
self._sql.execute(f"ROLLBACK TO SAVEPOINT {savepoint_name}"
|
|
657
|
+
)
|
|
658
|
+
|
|
659
|
+
stack.pop()
|
|
660
|
+
_tx_stack.set(stack)
|
|
661
|
+
|
|
596
662
|
def count(self):
|
|
597
663
|
"""Count how much objects/rows stored in this table"""
|
|
598
664
|
# ? Might as well uses __len__? But it's quite expensive.
|
|
@@ -601,13 +667,4 @@ constraint is enabled."
|
|
|
601
667
|
def __repr__(self) -> str:
|
|
602
668
|
return f"<Table({self._table}) -> {self._parent_repr}>"
|
|
603
669
|
|
|
604
|
-
class AsyncTable(Table):
|
|
605
|
-
"""Async (threads, subprocess) ready"""
|
|
606
|
-
|
|
607
|
-
def __enter__(self):
|
|
608
|
-
return self
|
|
609
|
-
|
|
610
|
-
def __exit__(self, exc_type, _, __):
|
|
611
|
-
pass
|
|
612
|
-
|
|
613
670
|
__all__ = ["Table"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlite_database
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.8
|
|
4
4
|
Summary: A weird wrapper for SQLite Connection
|
|
5
5
|
Home-page: https://github.com/RimuEirnarn/sqlite_database
|
|
6
6
|
Author: RimuEirnarn
|
|
@@ -79,6 +79,29 @@ You can read why this library exists by reading [the history](History.md). The p
|
|
|
79
79
|
|
|
80
80
|
You can submit any issue if you found a good issue. You can submit a pull request as long as the thing you want complies with what this project aims for.
|
|
81
81
|
|
|
82
|
+
## Development
|
|
83
|
+
|
|
84
|
+
When pulling this repository with latest commit, make sure to install all `dev-requirements.txt` depending on your intentions. It's mostly pylint and pytest.
|
|
85
|
+
|
|
86
|
+
When using `pytest`, do this: `pytest --config-file=./dev-config/pytest.ini` or check any check scripts in `bin/`
|
|
87
|
+
|
|
88
|
+
### How to install?
|
|
89
|
+
|
|
90
|
+
```sh
|
|
91
|
+
git clone https://github.com/RimuEirnarn/sqlite_database
|
|
92
|
+
cd sqlite_database
|
|
93
|
+
|
|
94
|
+
python -m venv .venv
|
|
95
|
+
.venv/bin/activate
|
|
96
|
+
|
|
97
|
+
pip install -r ./dev-requirements.txt
|
|
98
|
+
./bin/check.sh
|
|
99
|
+
|
|
100
|
+
# Above script is equivalent to:
|
|
101
|
+
pylint --rcfile ./dev-config/pylint.toml sqlite_database
|
|
102
|
+
pytest --config-file ./dev-config/pytest.ini
|
|
103
|
+
```
|
|
104
|
+
|
|
82
105
|
## License
|
|
83
106
|
|
|
84
107
|
This library/wrapper/repo/project is licensed with/in BSD 3-Clause "New" or "Revised" License.
|
|
@@ -82,6 +82,9 @@ sqlite_database.egg-info/dependency_links.txt
|
|
|
82
82
|
sqlite_database.egg-info/requires.txt
|
|
83
83
|
sqlite_database.egg-info/top_level.txt
|
|
84
84
|
sqlite_database.egg-info/zip-safe
|
|
85
|
+
sqlite_database/asyncs/__init__.py
|
|
86
|
+
sqlite_database/asyncs/database.py
|
|
87
|
+
sqlite_database/asyncs/table.py
|
|
85
88
|
sqlite_database/models/__init__.py
|
|
86
89
|
sqlite_database/models/errors.py
|
|
87
90
|
sqlite_database/models/helpers.py
|
|
@@ -247,3 +247,18 @@ def test_model_api_query_builder_delete():
|
|
|
247
247
|
admin = Users.create(id=auto_id(), username='admin', is_active=True)
|
|
248
248
|
|
|
249
249
|
assert Users.where(id=admin.id).throw().delete() == 1, "Changes should be 1"
|
|
250
|
+
|
|
251
|
+
def test_model_api_custom_id():
|
|
252
|
+
"""Test Model API Custom Primary ID"""
|
|
253
|
+
|
|
254
|
+
db = Database(":memory:")
|
|
255
|
+
db.foreign_pragma('ON')
|
|
256
|
+
|
|
257
|
+
@model(db)
|
|
258
|
+
class Users(BaseModel):
|
|
259
|
+
__schema__ = (Primary('uid'),)
|
|
260
|
+
|
|
261
|
+
uid: str
|
|
262
|
+
username: str
|
|
263
|
+
|
|
264
|
+
assert Users.create(uid="1", username='admin')
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sqlite_database-0.7.6 → sqlite_database-0.7.8}/docs/sqlite_database.model.query_builder.rst
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sqlite_database-0.7.6 → sqlite_database-0.7.8}/sqlite_database.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|