sqlite-database 0.7.7__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.
Files changed (112) hide show
  1. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/.gitignore +1 -0
  2. {sqlite_database-0.7.7/sqlite_database.egg-info → sqlite_database-0.7.8}/PKG-INFO +24 -1
  3. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/README.md +23 -0
  4. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/TODO.md +5 -5
  5. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/sqlite_database/__init__.py +2 -1
  6. sqlite_database-0.7.8/sqlite_database/asyncs/__init__.py +3 -0
  7. sqlite_database-0.7.8/sqlite_database/asyncs/database.py +37 -0
  8. sqlite_database-0.7.8/sqlite_database/asyncs/table.py +95 -0
  9. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/sqlite_database/csv.py +13 -1
  10. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/sqlite_database/database.py +3 -30
  11. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/sqlite_database/models/__init__.py +3 -2
  12. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/sqlite_database/table.py +73 -16
  13. {sqlite_database-0.7.7 → sqlite_database-0.7.8/sqlite_database.egg-info}/PKG-INFO +24 -1
  14. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/sqlite_database.egg-info/SOURCES.txt +3 -0
  15. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/.editorconfig +0 -0
  16. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  17. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  18. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/.github/ISSUE_TEMPLATE/question.md +0 -0
  19. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/.github/dependabot.yml +0 -0
  20. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/.github/workflows/pylint.yml +0 -0
  21. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/.github/workflows/pytest.yml +0 -0
  22. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/.github/workflows/python-publish.yml +0 -0
  23. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/.readthedocs.yaml +0 -0
  24. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/.vscode/settings.json +0 -0
  25. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/Features.md +0 -0
  26. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/History.md +0 -0
  27. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/LICENSE +0 -0
  28. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/SimpleGuide.md +0 -0
  29. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/bin/activate +0 -0
  30. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/bin/check.bat +0 -0
  31. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/bin/check.sh +0 -0
  32. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/bin/include/utility.bash +0 -0
  33. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/bin/install.bash +0 -0
  34. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/bin/need-installed/activate +0 -0
  35. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/bin/need-installed/pre-commit +0 -0
  36. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/bin/summarize-pylint.py +0 -0
  37. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/dev-config/black.toml +0 -0
  38. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/dev-config/pylint.toml +0 -0
  39. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/dev-config/pytest.ini +0 -0
  40. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/dev-requirements.txt +0 -0
  41. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/docs/Makefile +0 -0
  42. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/docs/ModelAPI.md +0 -0
  43. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/docs/SimpleGuide.md +0 -0
  44. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/docs/_.md +0 -0
  45. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/docs/api_reference.rst +0 -0
  46. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/docs/conf.py +0 -0
  47. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/docs/index.rst +0 -0
  48. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/docs/make.bat +0 -0
  49. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/docs/modules.rst +0 -0
  50. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/docs/sqlite_database.column.rst +0 -0
  51. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/docs/sqlite_database.config.rst +0 -0
  52. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/docs/sqlite_database.csv.rst +0 -0
  53. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/docs/sqlite_database.database.rst +0 -0
  54. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/docs/sqlite_database.errors.rst +0 -0
  55. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/docs/sqlite_database.functions.rst +0 -0
  56. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/docs/sqlite_database.locals.rst +0 -0
  57. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/docs/sqlite_database.model.errors.rst +0 -0
  58. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/docs/sqlite_database.model.helpers.rst +0 -0
  59. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/docs/sqlite_database.model.query_builder.rst +0 -0
  60. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/docs/sqlite_database.model.rst +0 -0
  61. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/docs/sqlite_database.operators.rst +0 -0
  62. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/docs/sqlite_database.query_builder.rst +0 -0
  63. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/docs/sqlite_database.rst +0 -0
  64. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/docs/sqlite_database.signature.rst +0 -0
  65. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/docs/sqlite_database.subexp.rst +0 -0
  66. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/docs/sqlite_database.table.rst +0 -0
  67. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/docs/sqlite_database.typings.rst +0 -0
  68. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/docs/sqlite_database.utils.rst +0 -0
  69. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/docs-requirements.txt +0 -0
  70. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/project-init.bash +0 -0
  71. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/pyproject.toml +0 -0
  72. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/setup.cfg +0 -0
  73. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/setup.py +0 -0
  74. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/sqlite_database/_debug.py +0 -0
  75. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/sqlite_database/_utils.py +0 -0
  76. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/sqlite_database/column.py +0 -0
  77. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/sqlite_database/errors.py +0 -0
  78. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/sqlite_database/functions.py +0 -0
  79. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/sqlite_database/locals.py +0 -0
  80. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/sqlite_database/models/errors.py +0 -0
  81. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/sqlite_database/models/helpers.py +0 -0
  82. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/sqlite_database/models/mixin.py +0 -0
  83. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/sqlite_database/models/query_builder.py +0 -0
  84. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/sqlite_database/models/type_checkers.py +0 -0
  85. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/sqlite_database/operators.py +0 -0
  86. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/sqlite_database/query_builder.py +0 -0
  87. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/sqlite_database/signature.py +0 -0
  88. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/sqlite_database/subquery.py +0 -0
  89. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/sqlite_database/typings.py +0 -0
  90. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/sqlite_database/utils.py +0 -0
  91. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/sqlite_database.egg-info/dependency_links.txt +0 -0
  92. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/sqlite_database.egg-info/requires.txt +0 -0
  93. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/sqlite_database.egg-info/top_level.txt +0 -0
  94. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/sqlite_database.egg-info/zip-safe +0 -0
  95. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/tests/__init__.py +0 -0
  96. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/tests/database/__init__.py +0 -0
  97. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/tests/database/model_api/__init__.py +0 -0
  98. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/tests/database/model_api/test_model_api.py +0 -0
  99. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/tests/database/setup.py +0 -0
  100. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/tests/database/table_api/__init__.py +0 -0
  101. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/tests/database/table_api/test_csv.py +0 -0
  102. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/tests/database/table_api/test_delete.py +0 -0
  103. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/tests/database/table_api/test_insert.py +0 -0
  104. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/tests/database/table_api/test_others.py +0 -0
  105. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/tests/database/table_api/test_select.py +0 -0
  106. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/tests/database/table_api/test_update.py +0 -0
  107. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/tests/database/test_custom.py +0 -0
  108. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/tests/manual_test_performances.py +0 -0
  109. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/tests/test_internals.py +0 -0
  110. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/tests/user_benchmark.py +0 -0
  111. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/tests/user_helpers.py +0 -0
  112. {sqlite_database-0.7.7 → sqlite_database-0.7.8}/transient/README.md +0 -0
@@ -174,3 +174,4 @@ perf-counter.txt
174
174
 
175
175
  /transient/*
176
176
  !/transient/README.md
177
+ transient.db
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlite_database
3
- Version: 0.7.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
- - [ ] sqlite aggregate function support
13
- - [ ] sqlite window function support
14
- - [ ] sqlite collation support
12
+ - [x] sqlite aggregate function support
13
+ - [x] sqlite window function support
14
+ - [x] sqlite collation support
15
15
  - [x] sqlite pragma
16
- - [ ] sqlite branching subquery (??)
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
- - [ ] CSV Import
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.7"
18
+ __version__ = "0.7.8"
18
19
  __all__ = [
19
20
  "Database",
20
21
  "Table",
@@ -0,0 +1,3 @@
1
+ """Asyncs"""
2
+ from .table import AsyncTable
3
+ from .database import AsyncDatabase
@@ -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, Connection
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 = Table(self, table, __columns)
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
@@ -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
- def get_table(self):
366
+ @classmethod
367
+ def get_table(cls):
367
368
  """Return table instance"""
368
- return self._tbl
369
+ return cls._tbl
369
370
 
370
371
  @classmethod
371
372
  def where(cls, **kwargs):
@@ -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 sqlite3 import Connection, OperationalError
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.execute("BEGIN TRANSACTION")
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._sql.commit()
107
+ self._commit_transaction()
108
+ self._dirty = False
89
109
  else:
90
- self._sql.rollback()
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 OperationalError as exc:
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.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
File without changes