python-files-db
Advanced tools
+201
| Apache License | ||
| Version 2.0, January 2004 | ||
| http://www.apache.org/licenses/ | ||
| TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | ||
| 1. Definitions. | ||
| "License" shall mean the terms and conditions for use, reproduction, | ||
| and distribution as defined by Sections 1 through 9 of this document. | ||
| "Licensor" shall mean the copyright owner or entity authorized by | ||
| the copyright owner that is granting the License. | ||
| "Legal Entity" shall mean the union of the acting entity and all | ||
| other entities that control, are controlled by, or are under common | ||
| control with that entity. For the purposes of this definition, | ||
| "control" means (i) the power, direct or indirect, to cause the | ||
| direction or management of such entity, whether by contract or | ||
| otherwise, or (ii) ownership of fifty percent (50%) or more of the | ||
| outstanding shares, or (iii) beneficial ownership of such entity. | ||
| "You" (or "Your") shall mean an individual or Legal Entity | ||
| exercising permissions granted by this License. | ||
| "Source" form shall mean the preferred form for making modifications, | ||
| including but not limited to software source code, documentation | ||
| source, and configuration files. | ||
| "Object" form shall mean any form resulting from mechanical | ||
| transformation or translation of a Source form, including but | ||
| not limited to compiled object code, generated documentation, | ||
| and conversions to other media types. | ||
| "Work" shall mean the work of authorship, whether in Source or | ||
| Object form, made available under the License, as indicated by a | ||
| copyright notice that is included in or attached to the work | ||
| (an example is provided in the Appendix below). | ||
| "Derivative Works" shall mean any work, whether in Source or Object | ||
| form, that is based on (or derived from) the Work and for which the | ||
| editorial revisions, annotations, elaborations, or other modifications | ||
| represent, as a whole, an original work of authorship. For the purposes | ||
| of this License, Derivative Works shall not include works that remain | ||
| separable from, or merely link (or bind by name) to the interfaces of, | ||
| the Work and Derivative Works thereof. | ||
| "Contribution" shall mean any work of authorship, including | ||
| the original version of the Work and any modifications or additions | ||
| to that Work or Derivative Works thereof, that is intentionally | ||
| submitted to Licensor for inclusion in the Work by the copyright owner | ||
| or by an individual or Legal Entity authorized to submit on behalf of | ||
| the copyright owner. For the purposes of this definition, "submitted" | ||
| means any form of electronic, verbal, or written communication sent | ||
| to the Licensor or its representatives, including but not limited to | ||
| communication on electronic mailing lists, source code control systems, | ||
| and issue tracking systems that are managed by, or on behalf of, the | ||
| Licensor for the purpose of discussing and improving the Work, but | ||
| excluding communication that is conspicuously marked or otherwise | ||
| designated in writing by the copyright owner as "Not a Contribution." | ||
| "Contributor" shall mean Licensor and any individual or Legal Entity | ||
| on behalf of whom a Contribution has been received by Licensor and | ||
| subsequently incorporated within the Work. | ||
| 2. Grant of Copyright License. Subject to the terms and conditions of | ||
| this License, each Contributor hereby grants to You a perpetual, | ||
| worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||
| copyright license to reproduce, prepare Derivative Works of, | ||
| publicly display, publicly perform, sublicense, and distribute the | ||
| Work and such Derivative Works in Source or Object form. | ||
| 3. Grant of Patent License. Subject to the terms and conditions of | ||
| this License, each Contributor hereby grants to You a perpetual, | ||
| worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||
| (except as stated in this section) patent license to make, have made, | ||
| use, offer to sell, sell, import, and otherwise transfer the Work, | ||
| where such license applies only to those patent claims licensable | ||
| by such Contributor that are necessarily infringed by their | ||
| Contribution(s) alone or by combination of their Contribution(s) | ||
| with the Work to which such Contribution(s) was submitted. If You | ||
| institute patent litigation against any entity (including a | ||
| cross-claim or counterclaim in a lawsuit) alleging that the Work | ||
| or a Contribution incorporated within the Work constitutes direct | ||
| or contributory patent infringement, then any patent licenses | ||
| granted to You under this License for that Work shall terminate | ||
| as of the date such litigation is filed. | ||
| 4. Redistribution. You may reproduce and distribute copies of the | ||
| Work or Derivative Works thereof in any medium, with or without | ||
| modifications, and in Source or Object form, provided that You | ||
| meet the following conditions: | ||
| (a) You must give any other recipients of the Work or | ||
| Derivative Works a copy of this License; and | ||
| (b) You must cause any modified files to carry prominent notices | ||
| stating that You changed the files; and | ||
| (c) You must retain, in the Source form of any Derivative Works | ||
| that You distribute, all copyright, patent, trademark, and | ||
| attribution notices from the Source form of the Work, | ||
| excluding those notices that do not pertain to any part of | ||
| the Derivative Works; and | ||
| (d) If the Work includes a "NOTICE" text file as part of its | ||
| distribution, then any Derivative Works that You distribute must | ||
| include a readable copy of the attribution notices contained | ||
| within such NOTICE file, excluding those notices that do not | ||
| pertain to any part of the Derivative Works, in at least one | ||
| of the following places: within a NOTICE text file distributed | ||
| as part of the Derivative Works; within the Source form or | ||
| documentation, if provided along with the Derivative Works; or, | ||
| within a display generated by the Derivative Works, if and | ||
| wherever such third-party notices normally appear. The contents | ||
| of the NOTICE file are for informational purposes only and | ||
| do not modify the License. You may add Your own attribution | ||
| notices within Derivative Works that You distribute, alongside | ||
| or as an addendum to the NOTICE text from the Work, provided | ||
| that such additional attribution notices cannot be construed | ||
| as modifying the License. | ||
| You may add Your own copyright statement to Your modifications and | ||
| may provide additional or different license terms and conditions | ||
| for use, reproduction, or distribution of Your modifications, or | ||
| for any such Derivative Works as a whole, provided Your use, | ||
| reproduction, and distribution of the Work otherwise complies with | ||
| the conditions stated in this License. | ||
| 5. Submission of Contributions. Unless You explicitly state otherwise, | ||
| any Contribution intentionally submitted for inclusion in the Work | ||
| by You to the Licensor shall be under the terms and conditions of | ||
| this License, without any additional terms or conditions. | ||
| Notwithstanding the above, nothing herein shall supersede or modify | ||
| the terms of any separate license agreement you may have executed | ||
| with Licensor regarding such Contributions. | ||
| 6. Trademarks. This License does not grant permission to use the trade | ||
| names, trademarks, service marks, or product names of the Licensor, | ||
| except as required for reasonable and customary use in describing the | ||
| origin of the Work and reproducing the content of the NOTICE file. | ||
| 7. Disclaimer of Warranty. Unless required by applicable law or | ||
| agreed to in writing, Licensor provides the Work (and each | ||
| Contributor provides its Contributions) on an "AS IS" BASIS, | ||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | ||
| implied, including, without limitation, any warranties or conditions | ||
| of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | ||
| PARTICULAR PURPOSE. You are solely responsible for determining the | ||
| appropriateness of using or redistributing the Work and assume any | ||
| risks associated with Your exercise of permissions under this License. | ||
| 8. Limitation of Liability. In no event and under no legal theory, | ||
| whether in tort (including negligence), contract, or otherwise, | ||
| unless required by applicable law (such as deliberate and grossly | ||
| negligent acts) or agreed to in writing, shall any Contributor be | ||
| liable to You for damages, including any direct, indirect, special, | ||
| incidental, or consequential damages of any character arising as a | ||
| result of this License or out of the use or inability to use the | ||
| Work (including but not limited to damages for loss of goodwill, | ||
| work stoppage, computer failure or malfunction, or any and all | ||
| other commercial damages or losses), even if such Contributor | ||
| has been advised of the possibility of such damages. | ||
| 9. Accepting Warranty or Additional Liability. While redistributing | ||
| the Work or Derivative Works thereof, You may choose to offer, | ||
| and charge a fee for, acceptance of support, warranty, indemnity, | ||
| or other liability obligations and/or rights consistent with this | ||
| License. However, in accepting such obligations, You may act only | ||
| on Your own behalf and on Your sole responsibility, not on behalf | ||
| of any other Contributor, and only if You agree to indemnify, | ||
| defend, and hold each Contributor harmless for any liability | ||
| incurred by, or claims asserted against, such Contributor by reason | ||
| of your accepting any such warranty or additional liability. | ||
| END OF TERMS AND CONDITIONS | ||
| APPENDIX: How to apply the Apache License to your work. | ||
| To apply the Apache License to your work, attach the following | ||
| boilerplate notice, with the fields enclosed by brackets "[]" | ||
| replaced with your own identifying information. (Don't include | ||
| the brackets!) The text should be enclosed in the appropriate | ||
| comment syntax for the file format. We also recommend that a | ||
| file or class name and description of purpose be included on the | ||
| same "printed page" as the copyright notice for easier | ||
| identification within third-party archives. | ||
| Copyright 2025 LangNeuron | ||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||
| you may not use this file except in compliance with the License. | ||
| You may obtain a copy of the License at | ||
| http://www.apache.org/licenses/LICENSE-2.0 | ||
| Unless required by applicable law or agreed to in writing, software | ||
| distributed under the License is distributed on an "AS IS" BASIS, | ||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| See the License for the specific language governing permissions and | ||
| limitations under the License. |
+128
| Metadata-Version: 2.4 | ||
| Name: python-files-db | ||
| Version: 0.0.1a5 | ||
| Summary: Lightweight file-system database for Python projects. | ||
| License-File: LICENSE | ||
| Keywords: database,filesystem,json,embedded,fastapi | ||
| Author: Anton Golubchikov | ||
| Author-email: anton1programmist@gmail.com | ||
| Requires-Python: >=3.12 | ||
| Classifier: Development Status :: 3 - Alpha | ||
| Classifier: Intended Audience :: Developers | ||
| Classifier: Programming Language :: Python :: 3 | ||
| Classifier: Operating System :: OS Independent | ||
| Requires-Dist: aiofiles (>=25.1.0,<26.0.0) | ||
| Project-URL: Bug Tracker, https://github.com/LangNeuron/pyfiles_db/issues | ||
| Project-URL: Homepage, https://github.com/LangNeuron/pyfiles_db | ||
| Description-Content-Type: text/markdown | ||
| # pyfiles_db | ||
| Lightweight file-system database for Python projects | ||
| ``` | ||
| PRE RELISE FIRST VERSION OF PYFILES_D | ||
| ``` | ||
| ## About | ||
| `pyfiles_db` is a lightweight and fast library that allows using the file system as a database for Python projects. It is minimalistic, requires no full-fledged DBMS, and is ideal for small to medium projects where simplicity and speed are priorities, and when system resources are a concern. It is built on top of `aiofiles` which allows you to use the file system as a database without any server required. Also can with synchronous mode. | ||
| ## Features | ||
| - Store data directly in folders and files (no server required) | ||
| - Simple API — quick to start | ||
| - Supports basic CRUD operations: Create, Read, Update, Delete | ||
| - Minimal external dependencies | ||
| - Designed for easy integration and flexibility | ||
| ## Installation | ||
| ```bash | ||
| pip install python-files-db | ||
| ``` | ||
| ## Quick Start | ||
| ```python | ||
| from pyfiles_db import FilesDB | ||
| file_db = FilesDB() | ||
| db = file_db.init_sync() # Or file_db.init_async() for async mode | ||
| # storage - path to database location, if is None use default path. | ||
| db.create_table( | ||
| "users", | ||
| columns={ | ||
| "id": "INT", | ||
| "name": "TEXT", | ||
| "age": "INT", | ||
| }, | ||
| id_generator="id", # required unique value of table, if None use generator for auto increment. | ||
| ) | ||
| db.new_data(table_name="users", data={ | ||
| "id": 1, | ||
| "name": "Anton", | ||
| "age": 17, | ||
| }) | ||
| db.new_data(table_name="users", data={ | ||
| "id": 2, | ||
| "name": "Alex", | ||
| "age": 17, | ||
| }) | ||
| user_id_1 = db.find("users", "id == 1") | ||
| # return [{"1": {"id": 1, "name": "Anton", "age": 17}}], 1 is file_id | ||
| user_id_2 = db.find("users", "id == 2") | ||
| # return [{"2": {"id": 2, "name": "Alex", "age": 17}}], 2 is file_id | ||
| users_age_17 = db.find("users", "age == 17") | ||
| # return [ | ||
| # {"1": {"id": 1, "name": "Anton", "age": 17}}, # 1 is file_id | ||
| # {"2": {"id": 2, "name": "Alex", "age": 17}, # 2 is file_id | ||
| # ] | ||
| ``` | ||
| Or async version: | ||
| ```python | ||
| from pyfiles_db import FilesDB | ||
| import asyncio | ||
| async def main(): | ||
| file_db = FilesDB() | ||
| db = file_db.init_async("path/to/folder") | ||
| await db.create_table(...) | ||
| await db.new_data(...) | ||
| res = await db.find(...) | ||
| asyncio.run(main()) | ||
| ``` | ||
| ## Use Cases | ||
| - Quick startups, prototypes, MVPs | ||
| - Lightweight web applications, scripts, utilities | ||
| - Projects not requiring a heavy DBMS | ||
| - Personal projects and data analysis tools | ||
| - low RAM and processor characteristics | ||
| ## Best Practices & Limitations | ||
| - Not designed for high-load systems with thousands of requests per second | ||
| - Maintain folder structure carefully to avoid naming collisions | ||
| - As this uses the file system, consider transaction/concurrency limitations | ||
| - Regularly back up the path folder | ||
| ## Contribution | ||
| Feedback, issues, and pull requests are welcome! | ||
| Follow the contribution guidelines if available. | ||
| Ensure your code is tested (tests in /tests/) and document changes. | ||
| ## License | ||
| This project is licensed under the Apache-2.0 License. See [LICENSE](https://github.com/LangNeuron/pyfiles_db/blob/main/LICENSE) for details. | ||
| ## Other | ||
| ### [CODE_OF_CONDUCT](https://github.com/LangNeuron/pyfiles_db/blob/main/CODE_OF_CONDUCT.md) | ||
| ### [CONTRIBUTING](https://github.com/LangNeuron/pyfiles_db/blob/main/CONTRIBUTING.md) | ||
| [project] | ||
| name = "python-files-db" | ||
| version = "0.0.1-alpha.5" | ||
| description = "Lightweight file-system database for Python projects." | ||
| authors = [ | ||
| {name = "Anton Golubchikov",email = "anton1programmist@gmail.com"} | ||
| ] | ||
| readme = "README.md" | ||
| requires-python = ">=3.12" | ||
| keywords = ["database", "filesystem", "json", "embedded", "fastapi"] | ||
| classifiers = [ | ||
| "Development Status :: 3 - Alpha", | ||
| "Intended Audience :: Developers", | ||
| "Programming Language :: Python :: 3", | ||
| "Operating System :: OS Independent" | ||
| ] | ||
| dependencies = [ | ||
| "aiofiles (>=25.1.0,<26.0.0)" | ||
| ] | ||
| license-files = ["LICENSE"] | ||
| [tool.poetry] | ||
| packages = [{include = "pyfiles_db", from = "src"}] | ||
| [build-system] | ||
| requires = ["poetry-core>=2.0.0,<3.0.0"] | ||
| build-backend = "poetry.core.masonry.api" | ||
| [project.urls] | ||
| "Homepage" = "https://github.com/LangNeuron/pyfiles_db" | ||
| "Bug Tracker" = "https://github.com/LangNeuron/pyfiles_db/issues" | ||
| # tools | ||
| [tool.ruff] | ||
| line-length = 80 | ||
| target-version = "py312" | ||
| [tool.ruff.lint] | ||
| select = ["ALL"] | ||
| [tool.ruff.lint.pycodestyle] | ||
| max-doc-length = 80 | ||
| [tool.ruff.lint.pydocstyle] | ||
| convention = "numpy" | ||
| # Mypy configuration | ||
| [tool.mypy] | ||
| mypy_path = "src" | ||
| explicit_package_bases = true | ||
| python_version = "3.12" | ||
| strict = true | ||
| warn_unused_configs = true | ||
| # pytets configuration | ||
| [tool.pytest.ini_options] | ||
| pythonpath = ["src/"] | ||
| [dependency-groups] | ||
| dev = [ | ||
| "ruff (>=0.14.0,<0.15.0)", | ||
| "mypy (>=1.18.2,<2.0.0)", | ||
| "pytest (>=8.4.2,<9.0.0)", | ||
| "types-aiofiles (>=25.1.0.20251011,<26.0.0.0)", | ||
| "pytest-asyncio (>=1.2.0,<2.0.0)" | ||
| ] |
+109
| # pyfiles_db | ||
| Lightweight file-system database for Python projects | ||
| ``` | ||
| PRE RELISE FIRST VERSION OF PYFILES_D | ||
| ``` | ||
| ## About | ||
| `pyfiles_db` is a lightweight and fast library that allows using the file system as a database for Python projects. It is minimalistic, requires no full-fledged DBMS, and is ideal for small to medium projects where simplicity and speed are priorities, and when system resources are a concern. It is built on top of `aiofiles` which allows you to use the file system as a database without any server required. Also can with synchronous mode. | ||
| ## Features | ||
| - Store data directly in folders and files (no server required) | ||
| - Simple API — quick to start | ||
| - Supports basic CRUD operations: Create, Read, Update, Delete | ||
| - Minimal external dependencies | ||
| - Designed for easy integration and flexibility | ||
| ## Installation | ||
| ```bash | ||
| pip install python-files-db | ||
| ``` | ||
| ## Quick Start | ||
| ```python | ||
| from pyfiles_db import FilesDB | ||
| file_db = FilesDB() | ||
| db = file_db.init_sync() # Or file_db.init_async() for async mode | ||
| # storage - path to database location, if is None use default path. | ||
| db.create_table( | ||
| "users", | ||
| columns={ | ||
| "id": "INT", | ||
| "name": "TEXT", | ||
| "age": "INT", | ||
| }, | ||
| id_generator="id", # required unique value of table, if None use generator for auto increment. | ||
| ) | ||
| db.new_data(table_name="users", data={ | ||
| "id": 1, | ||
| "name": "Anton", | ||
| "age": 17, | ||
| }) | ||
| db.new_data(table_name="users", data={ | ||
| "id": 2, | ||
| "name": "Alex", | ||
| "age": 17, | ||
| }) | ||
| user_id_1 = db.find("users", "id == 1") | ||
| # return [{"1": {"id": 1, "name": "Anton", "age": 17}}], 1 is file_id | ||
| user_id_2 = db.find("users", "id == 2") | ||
| # return [{"2": {"id": 2, "name": "Alex", "age": 17}}], 2 is file_id | ||
| users_age_17 = db.find("users", "age == 17") | ||
| # return [ | ||
| # {"1": {"id": 1, "name": "Anton", "age": 17}}, # 1 is file_id | ||
| # {"2": {"id": 2, "name": "Alex", "age": 17}, # 2 is file_id | ||
| # ] | ||
| ``` | ||
| Or async version: | ||
| ```python | ||
| from pyfiles_db import FilesDB | ||
| import asyncio | ||
| async def main(): | ||
| file_db = FilesDB() | ||
| db = file_db.init_async("path/to/folder") | ||
| await db.create_table(...) | ||
| await db.new_data(...) | ||
| res = await db.find(...) | ||
| asyncio.run(main()) | ||
| ``` | ||
| ## Use Cases | ||
| - Quick startups, prototypes, MVPs | ||
| - Lightweight web applications, scripts, utilities | ||
| - Projects not requiring a heavy DBMS | ||
| - Personal projects and data analysis tools | ||
| - low RAM and processor characteristics | ||
| ## Best Practices & Limitations | ||
| - Not designed for high-load systems with thousands of requests per second | ||
| - Maintain folder structure carefully to avoid naming collisions | ||
| - As this uses the file system, consider transaction/concurrency limitations | ||
| - Regularly back up the path folder | ||
| ## Contribution | ||
| Feedback, issues, and pull requests are welcome! | ||
| Follow the contribution guidelines if available. | ||
| Ensure your code is tested (tests in /tests/) and document changes. | ||
| ## License | ||
| This project is licensed under the Apache-2.0 License. See [LICENSE](https://github.com/LangNeuron/pyfiles_db/blob/main/LICENSE) for details. | ||
| ## Other | ||
| ### [CODE_OF_CONDUCT](https://github.com/LangNeuron/pyfiles_db/blob/main/CODE_OF_CONDUCT.md) | ||
| ### [CONTRIBUTING](https://github.com/LangNeuron/pyfiles_db/blob/main/CONTRIBUTING.md) |
| # Copyright 2025 LangNeuron | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| """Init library.""" | ||
| from .files_db import FilesDB | ||
| __all__ = ["FilesDB"] |
| # Copyright 2025 LangNeuron | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| """Init database managers.""" | ||
| from ._db import _DB | ||
| from .async_db import _DBasync | ||
| from .meta import META | ||
| from .sync_db import _DBsync | ||
| __all__ = ["META", "_DB", "_DBasync", "_DBsync"] |
| # Copyright 2025 LangNeuron | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| """Abstrct database manager.""" | ||
| from abc import ABC, abstractmethod | ||
| from collections.abc import Coroutine | ||
| from pathlib import Path | ||
| from typing import Any | ||
| class _DB(ABC): | ||
| @abstractmethod | ||
| def __init__(self, storage: str | Path, meta_file: str) -> None: | ||
| """Init database. | ||
| Parameters | ||
| ---------- | ||
| storage : str | Path | ||
| path to db location | ||
| meta_file : str | ||
| name of meta file | ||
| """ | ||
| @abstractmethod | ||
| def create_table(self, table_name: str, | ||
| columns: dict[str, str], | ||
| id_generator: str | int | None = None, | ||
| ) -> None | Coroutine[Any, Any, None]: | ||
| """Create a new table. | ||
| Parameters | ||
| ---------- | ||
| table_name : str | ||
| name of table | ||
| columns : dict[str, str] | ||
| columns with data type | ||
| id_generator : str, None | ||
| default None | ||
| str is name of column data when need use how nameing of file | ||
| None use simple id generator (increment, not recominded) | ||
| """ | ||
| @abstractmethod | ||
| def new_data(self, table_name: str, data: dict[str, Any]) -> None: | ||
| """Add new data to database. | ||
| Parameters | ||
| ---------- | ||
| tabel : str | ||
| name of data table | ||
| data : dict[str, Any] | ||
| information when need save | ||
| """ | ||
| @abstractmethod | ||
| def find(self, table_name: str, condition: str) -> list[dict[str, Any]]: | ||
| """Find information in database. | ||
| Parameters | ||
| ---------- | ||
| table_name : str | ||
| name of table | ||
| condition : str | ||
| maybe is "id == 1" | ||
| Returns | ||
| ------- | ||
| list[dict[str, Any]] | ||
| all data in table | ||
| """ | ||
| @abstractmethod | ||
| def update(self, | ||
| table_name: str, | ||
| file_id: str, | ||
| new_data: dict[str, Any], | ||
| ) -> None: | ||
| """Update data with file_id. | ||
| Get file_id from find method. | ||
| Parameters | ||
| ---------- | ||
| file_id : str | ||
| unique file name | ||
| new_data : dict[str, Any] | ||
| new data when need save | ||
| """ | ||
| @abstractmethod | ||
| def delete(self, | ||
| table_name: str, | ||
| file_id: str, | ||
| ) -> None: | ||
| """Delete data with file_id. | ||
| Parameters | ||
| ---------- | ||
| table_name : str | ||
| name of table db | ||
| file_id : str | ||
| name of file in table | ||
| """ | ||
| class _AsyncDB(ABC): | ||
| @abstractmethod | ||
| def __init__(self, storage: str | Path, meta_file: str) -> None: | ||
| """Initialize the asynchronous database manager. | ||
| Parameters | ||
| ---------- | ||
| storage : str | Path | ||
| Path to database location. | ||
| meta_file : str | ||
| Name of meta file. | ||
| """ | ||
| @abstractmethod | ||
| async def create_table(self, table_name: str, | ||
| columns: dict[str, str], | ||
| id_generator: str | int | None = None, | ||
| ) -> None: | ||
| """Create a new table (async). | ||
| Parameters | ||
| ---------- | ||
| table_name : str | ||
| Name of the table. | ||
| columns : dict[str, str] | ||
| Columns mapping to their data types. | ||
| id_generator : str | int | None | ||
| If a string, this is the column name used as file identifier. | ||
| If None, an integer auto-increment generator is used. | ||
| """ | ||
| @abstractmethod | ||
| async def new_data(self, table_name: str, data: dict[str, Any]) -> None: | ||
| """Add new data to the database (async). | ||
| Parameters | ||
| ---------- | ||
| table_name : str | ||
| Name of the table. | ||
| data : dict[str, Any] | ||
| Record to save. | ||
| """ | ||
| @abstractmethod | ||
| async def find(self, | ||
| table_name: str, | ||
| condition: str, | ||
| ) -> list[dict[str, Any]]: | ||
| """Find information in database. | ||
| Parameters | ||
| ---------- | ||
| table_name : str | ||
| name of table | ||
| condition : str | ||
| maybe is "id == 1" | ||
| Returns | ||
| ------- | ||
| list[dict[str, Any]] | ||
| all data in table | ||
| """ | ||
| @abstractmethod | ||
| async def update(self, | ||
| table_name: str, | ||
| file_id: str, | ||
| new_data: dict[str, Any], | ||
| ) -> None: | ||
| """Update data with file_id. | ||
| Get file_id from find method. | ||
| Parameters | ||
| ---------- | ||
| file_id : str | ||
| unique file name | ||
| new_data : dict[str, Any] | ||
| new data when need save | ||
| """ | ||
| @abstractmethod | ||
| async def delete(self, | ||
| table_name: str, | ||
| file_id: str, | ||
| ) -> None: | ||
| """Delete data with file_id. | ||
| Parameters | ||
| ---------- | ||
| table_name : str | ||
| name of table db | ||
| file_id : str | ||
| name of file in table | ||
| """ |
| # Copyright 2025 LangNeuron | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| """Async database manager.""" | ||
| import json | ||
| from pathlib import Path | ||
| from typing import TYPE_CHECKING, Any | ||
| import aiofiles | ||
| from pyfiles_db.database_manager._db import _AsyncDB | ||
| from pyfiles_db.database_manager.meta import META | ||
| from pyfiles_db.errors import ( | ||
| DataIsUncorrectError, | ||
| NotFoundColumnError, | ||
| NotFoundTableError, | ||
| TableAlreadyAvaibleError, | ||
| UnknownDataTypeError, | ||
| ) | ||
| from pyfiles_db.utils import infinite_natural_numbers | ||
| if TYPE_CHECKING: | ||
| from collections.abc import Generator | ||
| class _DBasync(_AsyncDB): | ||
| def __init__(self, storage: str | Path, meta_file: str) -> None: | ||
| """Initialize the asynchronous database manager. | ||
| Parameters | ||
| ---------- | ||
| storage : str | Path | ||
| Path to the database location. | ||
| meta_file : str | ||
| Name of the meta file. | ||
| """ | ||
| self._storage = Path(storage) | ||
| self._meta_file = meta_file | ||
| self._id_generators: dict[str, Generator[Any, Any, Any]] = {} | ||
| self._load_meta() | ||
| def _load_meta(self) -> None: | ||
| """Load meta information from file.""" | ||
| with Path.open(self._storage / self._meta_file, "r") as f: | ||
| self._meta = json.load(f) | ||
| async def create_table( | ||
| self, table_name: str, | ||
| columns: dict[str, Any], | ||
| id_generator: str | int | None = None, | ||
| ) -> None: | ||
| """Create a table (async). | ||
| Parameters | ||
| ---------- | ||
| table_name : str | ||
| Name of the table. | ||
| columns : dict[str, str] | ||
| Columns mapping to their data types. | ||
| id_generator : str | None | ||
| Generator for file names. Default None. | ||
| Raises | ||
| ------ | ||
| TableAlreadyAvaibleError | ||
| If the table already exists. | ||
| """ | ||
| # Table. columns is maybe {"USER_ID": "INT", "NAME": "TEXT"} | ||
| table = self._meta[META.TABLE_PREFIX] + table_name | ||
| if table in self._meta[META.TABLES]: | ||
| raise TableAlreadyAvaibleError | ||
| if id_generator is None: | ||
| id_generator = 0 | ||
| self._id_generators[table] = infinite_natural_numbers(id_generator) | ||
| await self._mkdir_for_table(table) | ||
| self._meta[META.TABLES].append(table) | ||
| self._meta[table] = { | ||
| META.COLUMNS: columns, | ||
| META.GENERATOR: id_generator} | ||
| await self._update_meta() | ||
| async def _update_meta(self) -> None: | ||
| """Update meta file.""" | ||
| async with aiofiles.open( | ||
| self._storage / self._meta_file, | ||
| mode="w", | ||
| ) as f: | ||
| await f.write(json.dumps(self._meta)) | ||
| async def _mkdir_for_table(self, table: str | Path) -> None: | ||
| """Create the on-disk folder and index file for a table. | ||
| Parameters | ||
| ---------- | ||
| table : str | Path | ||
| Name of the table folder. | ||
| """ | ||
| (self._storage / table).mkdir(parents=False, exist_ok=True) | ||
| async with aiofiles.open( | ||
| self._storage / table / ".json", | ||
| mode="w", | ||
| ) as f: | ||
| await f.write(json.dumps({META.FILE_IDS: []})) | ||
| async def new_data(self, table_name: str, data: dict[str, Any]) -> None: | ||
| """Add new data to the table (async). | ||
| Parameters | ||
| ---------- | ||
| table_name : str | ||
| Name of the table. | ||
| data : dict[str, Any] | ||
| The record to save. | ||
| """ | ||
| table_name = self._meta[META.TABLE_PREFIX] + table_name | ||
| if not self._check_table(table_name): | ||
| raise NotFoundTableError(table_name=table_name) | ||
| if not self._check_data(self._meta[table_name][META.COLUMNS], data): | ||
| raise DataIsUncorrectError(data=data) | ||
| file_name = "" | ||
| if (self._meta[table_name][META.GENERATOR] is None or | ||
| isinstance(self._meta[table_name][META.GENERATOR], int)): | ||
| if self._id_generators.get(table_name) is None: | ||
| self._id_generators[table_name] = infinite_natural_numbers( | ||
| self._meta[table_name][META.GENERATOR]) | ||
| file_name = next(self._id_generators[table_name]) | ||
| self._meta[table_name][META.GENERATOR] += 1 | ||
| await self._update_meta() | ||
| else: | ||
| file_name = data[self._meta[table_name][META.GENERATOR]] | ||
| async with aiofiles.open( | ||
| self._storage / table_name / f"{file_name}.json", | ||
| mode="w") as f: | ||
| await f.write(json.dumps(data)) | ||
| async with aiofiles.open( | ||
| self._storage / table_name / ".json") as f: | ||
| content = await f.read() | ||
| data = json.loads(content) | ||
| data[META.FILE_IDS].append(file_name) | ||
| async with aiofiles.open( | ||
| self._storage / table_name / ".json", mode="w") as f: | ||
| await f.write(json.dumps(data)) | ||
| def _check_table(self, table: str) -> bool: | ||
| """Check table for exists. | ||
| Parameters | ||
| ---------- | ||
| table : str | ||
| name of table | ||
| Returns | ||
| ------- | ||
| bool | ||
| exist table | ||
| """ | ||
| return table in self._meta[META.TABLES] | ||
| def _check_data(self, columns: dict[str, str], | ||
| data: dict[str, Any]) -> bool: | ||
| """Check type data. | ||
| Parameters | ||
| ---------- | ||
| columns : dict[str, str] | ||
| col of table | ||
| data : dict[str, Any] | ||
| new information | ||
| Returns | ||
| ------- | ||
| bool | ||
| Data is correct | ||
| """ | ||
| for key, val in data.items(): | ||
| if (self._change_type(val, columns[key]) != val): | ||
| return False | ||
| return True | ||
| def _change_type(self, value: str, column_type: str) -> Any: # noqa: ANN401 | ||
| """Change data type. | ||
| Parameters | ||
| ---------- | ||
| value : str | ||
| value | ||
| column_type : str | ||
| data type | ||
| Returns | ||
| ------- | ||
| Any | ||
| correct data type | ||
| Raises | ||
| ------ | ||
| ValueError | ||
| if column_type is unknown | ||
| """ | ||
| match column_type: | ||
| case "INT": | ||
| return int(value) | ||
| case "TEXT": | ||
| return str(value) | ||
| case _: | ||
| raise UnknownDataTypeError | ||
| async def find(self, | ||
| table_name: str, | ||
| condition: str, | ||
| ) -> list[dict[str, Any]]: | ||
| """Find information in table. | ||
| Parameters | ||
| ---------- | ||
| table_name : str | ||
| name of table | ||
| condition : str | ||
| condition, maybe "id == 5" | ||
| Returns | ||
| ------- | ||
| list[dict[str, Any]] | ||
| all data when find condition | ||
| Raises | ||
| ------ | ||
| ValueError | ||
| Table not found error | ||
| ValueError | ||
| Column not found error | ||
| """ | ||
| table_name = self._meta[META.TABLE_PREFIX] + table_name | ||
| if not self._check_table(table_name): | ||
| raise NotFoundTableError(table_name=table_name) | ||
| column_name, value = condition.replace(" ", "").split("==") | ||
| if not self._check_column_in_table(table_name, column_name): | ||
| raise NotFoundColumnError(column_name=column_name, | ||
| table_name=table_name) | ||
| value = self._change_type(value, | ||
| self._meta[table_name][META.COLUMNS][column_name]) | ||
| if self._meta[table_name][META.GENERATOR] == column_name: | ||
| async with aiofiles.open( | ||
| self._storage / table_name / f"{value}.json") as f: | ||
| content = await f.read() | ||
| data = json.loads(content) | ||
| if isinstance(data, dict): | ||
| return [{str(value): data}] | ||
| return [] | ||
| async with aiofiles.open( | ||
| self._storage / table_name / ".json") as f: | ||
| content = await f.read() | ||
| data = json.loads(content) | ||
| names = data[META.FILE_IDS] | ||
| result: list[dict[str, Any]] = [] | ||
| for name in names: | ||
| async with aiofiles.open( | ||
| self._storage / table_name / f"{name}.json") as f: | ||
| content = await f.read() | ||
| d = json.loads(content) | ||
| if d[column_name] == value and isinstance(d, dict): | ||
| result.append({str(name): d}) | ||
| return result | ||
| def _check_column_in_table(self, table_name: str, column_name: str) -> bool: | ||
| """Check column in table on exist. | ||
| Parameters | ||
| ---------- | ||
| table_name : str | ||
| name of table | ||
| column_name : str | ||
| name of column | ||
| Returns | ||
| ------- | ||
| bool | ||
| exist column | ||
| """ | ||
| return column_name in self._meta[table_name][META.COLUMNS] | ||
| async def update(self, | ||
| table_name: str, | ||
| file_id: str, | ||
| new_data: dict[str, Any], | ||
| ) -> None: | ||
| """Update data with file_id. | ||
| Get file_id from find method. | ||
| Parameters | ||
| ---------- | ||
| file_id : str | ||
| unique file name | ||
| new_data : dict[str, Any] | ||
| new data when need save | ||
| """ | ||
| table_name = self._meta[META.TABLE_PREFIX] + table_name | ||
| async with aiofiles.open( | ||
| self._storage / table_name / f"{file_id}.json", | ||
| mode="w") as f: | ||
| await f.write(json.dumps(new_data)) | ||
| async def delete(self, | ||
| table_name: str, | ||
| file_id: str, | ||
| ) -> None: | ||
| """Delete data with file_id. | ||
| Parameters | ||
| ---------- | ||
| table_name : str | ||
| name of table db | ||
| file_id : str | ||
| name of file in table | ||
| """ | ||
| table_name = self._meta[META.TABLE_PREFIX] + table_name | ||
| if not (self._storage / table_name / f"{file_id}.json").exists(): | ||
| raise FileNotFoundError | ||
| (self._storage / table_name / f"{file_id}.json").unlink() |
| # Copyright 2025 LangNeuron | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| """Sync database manager.""" | ||
| from dataclasses import dataclass | ||
| @dataclass | ||
| class META: | ||
| """Meta data for the database.""" | ||
| TABLES: str = "TABLES" | ||
| ENCRYPTDB: str = "ENCRYPTDB" | ||
| COLUMNS: str = "COLUMNS" | ||
| TABLE_PREFIX: str = "TABLE_PREFIX" | ||
| GENERATOR: str = "GENERATOR" | ||
| FILE_IDS: str = "FILE_IDS" |
| # Copyright 2025 LangNeuron | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| """Sync database manager.""" | ||
| import json | ||
| from pathlib import Path | ||
| from typing import TYPE_CHECKING, Any | ||
| from pyfiles_db.database_manager._db import _DB | ||
| from pyfiles_db.database_manager.meta import META | ||
| from pyfiles_db.errors import ( | ||
| DataIsUncorrectError, | ||
| NotFoundColumnError, | ||
| NotFoundTableError, | ||
| TableAlreadyAvaibleError, | ||
| UnknownDataTypeError, | ||
| ) | ||
| from pyfiles_db.utils import infinite_natural_numbers | ||
| if TYPE_CHECKING: | ||
| from collections.abc import Generator | ||
| class _DBsync(_DB): | ||
| def __init__(self, storage: str | Path, meta_file: str) -> None: | ||
| """Initialize the synchronous database manager. | ||
| Parameters | ||
| ---------- | ||
| storage : str | Path | ||
| Path to the database location. | ||
| meta_file : str | ||
| Name of the meta file. | ||
| """ | ||
| self._storage = Path(storage) | ||
| self._meta_file = meta_file | ||
| self._load_meta() | ||
| self._id_generators: dict[str, Generator[Any, Any, Any]] = {} | ||
| def _load_meta(self) -> None: | ||
| """Load meta information from file.""" | ||
| with Path.open(self._storage / self._meta_file, "r") as f: | ||
| self._meta = json.load(f) | ||
| def create_table(self, table_name: str, columns: dict[str, str], | ||
| id_generator: str | int | None = None) -> None: | ||
| """Create a table (sync). | ||
| Parameters | ||
| ---------- | ||
| table_name : str | ||
| Name of the table. | ||
| columns : dict[str, str] | ||
| Columns mapping to their data types. | ||
| id_generator : str | None | ||
| Generator for file names. Default None. | ||
| Raises | ||
| ------ | ||
| TableAlreadyAvaibleError | ||
| If the table already exists. | ||
| """ | ||
| # Table. columns is maybe {"USER_ID": "INT", "NAME": "TEXT"} | ||
| table = self._meta[META.TABLE_PREFIX] + table_name | ||
| if table in self._meta[META.TABLES]: | ||
| raise TableAlreadyAvaibleError | ||
| if id_generator is None: | ||
| id_generator = 0 | ||
| self._id_generators[table] = infinite_natural_numbers(id_generator) | ||
| self._mkdir_for_table(table) | ||
| self._meta[META.TABLES].append(table) | ||
| self._meta[table] = { | ||
| META.COLUMNS: columns, | ||
| META.GENERATOR: id_generator} | ||
| self._update_meta() | ||
| def _update_meta(self) -> None: | ||
| """Update meta file.""" | ||
| with Path.open(self._storage / self._meta_file, mode="w") as f: | ||
| json.dump(self._meta, f) | ||
| def _mkdir_for_table(self, table: str | Path) -> None: | ||
| """Create the on-disk folder and index file for a table. | ||
| Parameters | ||
| ---------- | ||
| table : str | Path | ||
| Name of the table folder. | ||
| """ | ||
| (self._storage / table).mkdir(parents=False, exist_ok=True) | ||
| with Path.open(self._storage / table / ".json", mode="w") as f: | ||
| json.dump({META.FILE_IDS: []}, f) | ||
| def new_data(self, table_name: str, data: dict[str, Any]) -> None: | ||
| """Save new data to the table. | ||
| Parameters | ||
| ---------- | ||
| table_name : str | ||
| Name of the table. | ||
| data : dict[str, Any] | ||
| Record to save. | ||
| """ | ||
| table_name = self._meta[META.TABLE_PREFIX] + table_name | ||
| if not self._check_table(table_name): | ||
| raise NotFoundTableError(table_name=table_name) | ||
| if not self._check_data(self._meta[table_name][META.COLUMNS], data): | ||
| raise DataIsUncorrectError(data=data) | ||
| file_name = "" | ||
| if (self._meta[table_name][META.GENERATOR] is None or | ||
| isinstance(self._meta[table_name][META.GENERATOR], int)): | ||
| if self._id_generators.get(table_name) is None: | ||
| self._id_generators[table_name] = infinite_natural_numbers( | ||
| self._meta[table_name][META.GENERATOR]) | ||
| file_name = next(self._id_generators[table_name]) | ||
| self._meta[table_name][META.GENERATOR] += 1 | ||
| self._update_meta() | ||
| else: | ||
| file_name = data[self._meta[table_name][META.GENERATOR]] | ||
| with Path.open( | ||
| self._storage / table_name / f"{file_name}.json", | ||
| mode="w") as f: | ||
| json.dump(data, f) | ||
| with Path.open(self._storage / table_name / ".json", mode="r") as f: | ||
| data = json.load(f) | ||
| data[META.FILE_IDS].append(file_name) | ||
| with Path.open(self._storage / table_name / ".json", mode="w") as f: | ||
| json.dump(data, f) | ||
| def _check_table(self, table: str) -> bool: | ||
| """Check whether a table exists. | ||
| Parameters | ||
| ---------- | ||
| table : str | ||
| Name of the table. | ||
| Returns | ||
| ------- | ||
| bool | ||
| True if the table exists. | ||
| """ | ||
| return table in self._meta[META.TABLES] | ||
| def _check_data(self, columns: dict[str, str], | ||
| data: dict[str, Any]) -> bool: | ||
| """Validate data types for a record against table columns. | ||
| Parameters | ||
| ---------- | ||
| columns : dict[str, str] | ||
| Column definitions for the table. | ||
| data : dict[str, Any] | ||
| Record to validate. | ||
| Returns | ||
| ------- | ||
| bool | ||
| True if the record matches the declared column types. | ||
| """ | ||
| for key, val in data.items(): | ||
| if (self._change_type(val, columns[key]) != val): | ||
| return False | ||
| return True | ||
| def find(self, | ||
| table_name: str, | ||
| condition: str, | ||
| ) -> list[dict[str, Any]]: | ||
| """Find records in a table matching a simple condition. | ||
| Parameters | ||
| ---------- | ||
| table_name : str | ||
| Name of the table. | ||
| condition : str | ||
| Condition string, e.g. "id == 5". | ||
| Returns | ||
| ------- | ||
| list[dict[str, Any]] | ||
| Records that match the condition. | ||
| Raises | ||
| ------ | ||
| ValueError | ||
| Table not found or column not found. | ||
| """ | ||
| table_name = self._meta[META.TABLE_PREFIX] + table_name | ||
| if not self._check_table(table_name): | ||
| raise NotFoundTableError(table_name=table_name) | ||
| column_name, value = condition.replace(" ", "").split("==") | ||
| if not self._check_column_in_table(table_name, column_name): | ||
| raise NotFoundColumnError(column_name=column_name, | ||
| table_name=table_name) | ||
| value = self._change_type(value, | ||
| self._meta[table_name][META.COLUMNS][column_name]) | ||
| if self._meta[table_name][META.GENERATOR] == column_name: | ||
| with Path.open( | ||
| self._storage / table_name / f"{value}.json", | ||
| mode="r") as f: | ||
| data = json.load(f) | ||
| if isinstance(data, dict): | ||
| return [{str(value): data}] | ||
| return [] | ||
| with Path.open( | ||
| self._storage / table_name / ".json",mode="r") as f: | ||
| data = json.load(f) | ||
| names = data[META.FILE_IDS] | ||
| result: list[dict[str, Any]] = [] | ||
| for name in names: | ||
| with Path.open( | ||
| self._storage / table_name / f"{name}.json", | ||
| mode="r") as f: | ||
| d = json.load(f) | ||
| if d[column_name] == value and isinstance(d, dict): | ||
| result.append({str(name): d}) | ||
| return result | ||
| def _check_column_in_table(self, table_name: str, column_name: str) -> bool: | ||
| """Check column in table on exist. | ||
| Parameters | ||
| ---------- | ||
| table_name : str | ||
| name of table | ||
| column_name : str | ||
| name of column | ||
| Returns | ||
| ------- | ||
| bool | ||
| exist column | ||
| """ | ||
| return column_name in self._meta[table_name][META.COLUMNS] | ||
| def _change_type(self, value: str, column_type: str) -> Any: # noqa: ANN401 | ||
| """Change data type. | ||
| Parameters | ||
| ---------- | ||
| value : str | ||
| value | ||
| column_type : str | ||
| data type | ||
| Returns | ||
| ------- | ||
| Any | ||
| correct data type | ||
| Raises | ||
| ------ | ||
| ValueError | ||
| if column_type is unknown | ||
| """ | ||
| match column_type: | ||
| case "INT": | ||
| return int(value) | ||
| case "TEXT": | ||
| return str(value) | ||
| case _: | ||
| raise UnknownDataTypeError | ||
| def update(self, | ||
| table_name: str, | ||
| file_id: str, | ||
| new_data: dict[str, Any], | ||
| ) -> None: | ||
| """Update data with file_id. | ||
| Get file_id from find method. | ||
| Parameters | ||
| ---------- | ||
| file_id : str | ||
| unique file name | ||
| new_data : dict[str, Any] | ||
| new data when need save | ||
| """ | ||
| table_name = self._meta[META.TABLE_PREFIX] + table_name | ||
| with Path.open( | ||
| self._storage / table_name / f"{file_id}.json", | ||
| mode="w") as f: | ||
| json.dump(new_data, f) | ||
| def delete(self, | ||
| table_name: str, | ||
| file_id: str, | ||
| ) -> None: | ||
| """Delete data with file_id. | ||
| Parameters | ||
| ---------- | ||
| table_name : str | ||
| name of table db | ||
| file_id : str | ||
| name of file in table | ||
| """ | ||
| table_name = self._meta[META.TABLE_PREFIX] + table_name | ||
| if not (self._storage / table_name / f"{file_id}.json").exists(): | ||
| raise FileNotFoundError | ||
| (self._storage / table_name / f"{file_id}.json").unlink() |
| # Copyright 2025 LangNeuron | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| """Errors.""" | ||
| from .eror_path_not_avaible import PathNotAvaibleError | ||
| from .error_data_is_uncorrect import DataIsUncorrectError | ||
| from .error_db_not_loaded import DbNotLoadedError | ||
| from .error_not_found import NotFoundColumnError, NotFoundTableError | ||
| from .error_unknown_data_type import UnknownDataTypeError | ||
| from .table_already_exist import TableAlreadyAvaibleError | ||
| __all__ = [ | ||
| "DataIsUncorrectError", | ||
| "DbNotLoadedError", | ||
| "NotFoundColumnError", | ||
| "NotFoundTableError", | ||
| "PathNotAvaibleError", | ||
| "TableAlreadyAvaibleError", | ||
| "UnknownDataTypeError", | ||
| ] |
| # Copyright 2025 LangNeuron | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| """Path / storage related errors.""" | ||
| class PathNotAvaibleError(FileNotFoundError): | ||
| """Raised when the configured database storage path is not available. | ||
| Note: class name preserves historical spelling for backward | ||
| compatibility with existing code and tests. | ||
| """ | ||
| def __str__(self) -> str: | ||
| """Return a readable message for this exception.""" | ||
| return "Database file not available" |
| # Copyright 2025 LangNeuron | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| """Eror DataIsUncorrectError.""" | ||
| from typing import Any | ||
| class DataIsUncorrectError(Exception): | ||
| """Error DataIsUncorrectError. | ||
| Parameters | ||
| ---------- | ||
| Exception : _type_ | ||
| Base exception | ||
| """ | ||
| def __init__(self, data: dict[str, Any]) -> None: | ||
| """Init. | ||
| Parameters | ||
| ---------- | ||
| table_name : str | ||
| name of table, when noot found | ||
| """ | ||
| self.data = data | ||
| super().__init__(f"Data is uncorrect, data: '{data}'.") | ||
| def __str__(self) -> str: | ||
| """Print Exception. | ||
| Returns | ||
| ------- | ||
| str | ||
| String info message | ||
| """ | ||
| return f"Error: Data is uncorrect {self.data}" |
| # Copyright 2025 LangNeuron | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| """Eror DbNotLoadedError.""" | ||
| class DbNotLoadedError(Exception): | ||
| """Error DbNotLoadedError. | ||
| Parameters | ||
| ---------- | ||
| Exception : _type_ | ||
| Base exception | ||
| """ | ||
| def __str__(self) -> str: | ||
| """Print Exception. | ||
| Returns | ||
| ------- | ||
| str | ||
| String info message | ||
| """ | ||
| return "Database not loaded" |
| # Copyright 2025 LangNeuron | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| """Eror DbNotLoadedError.""" | ||
| class NotFoundTableError(Exception): | ||
| """Error NotFoundError. | ||
| Parameters | ||
| ---------- | ||
| Exception : _type_ | ||
| Base exception | ||
| """ | ||
| def __init__(self, table_name: str) -> None: | ||
| """Init. | ||
| Parameters | ||
| ---------- | ||
| table_name : str | ||
| name of table, when noot found | ||
| """ | ||
| self.table_name = table_name | ||
| super().__init__(f"Table '{table_name}' not found.") | ||
| def __str__(self) -> str: | ||
| """Print Exception. | ||
| Returns | ||
| ------- | ||
| str | ||
| String info message | ||
| """ | ||
| return f"ERROR: TABLE **'{self.table_name}'** not found" | ||
| class NotFoundColumnError(Exception): | ||
| """Error NotFoundError. | ||
| Parameters | ||
| ---------- | ||
| Exception : _type_ | ||
| Base exception | ||
| """ | ||
| def __init__(self, column_name: str, table_name: str) -> None: | ||
| """Init. | ||
| Parameters | ||
| ---------- | ||
| table_name : str | ||
| name of table, when noot found | ||
| """ | ||
| self.column_name = column_name | ||
| self.table_name = table_name | ||
| super().__init__(f"Column '{column_name}' not found in {table_name}.") | ||
| def __str__(self) -> str: | ||
| """Print Exception. | ||
| Returns | ||
| ------- | ||
| str | ||
| String info message | ||
| """ | ||
| return f"Column '{self.column_name}' not found in {self.table_name}." |
| # Copyright 2025 LangNeuron | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| """Eror UnknownDataTypeError.""" | ||
| class UnknownDataTypeError(Exception): | ||
| """Error UnknownDataTypeError. | ||
| Parameters | ||
| ---------- | ||
| Exception : _type_ | ||
| Base exception | ||
| """ | ||
| def __str__(self) -> str: | ||
| """Print Exception. | ||
| Returns | ||
| ------- | ||
| str | ||
| String info message | ||
| """ | ||
| return "Unknown data type." |
| # Copyright 2025 LangNeuron | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| """Errors for table already exists condition.""" | ||
| class TableAlreadyAvaibleError(Exception): | ||
| """Raised when trying to create a table that already exists. | ||
| Note: class name preserves historical spelling for backward | ||
| compatibility with existing code and tests. | ||
| """ | ||
| def __str__(self) -> str: | ||
| """Return a readable message for this exception.""" | ||
| return "Table already available" |
| # Copyright 2025 LangNeuron | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| """FilesDB.""" | ||
| from __future__ import annotations | ||
| from pyfiles_db.database_manager import META, _DBasync, _DBsync | ||
| try: | ||
| from typing import Self | ||
| except ImportError: | ||
| from typing_extensions import Self # noqa: UP035 for Python < 3.11 | ||
| import json | ||
| from pathlib import Path | ||
| from typing import TYPE_CHECKING, Any, ClassVar | ||
| from .errors import ( | ||
| PathNotAvaibleError, | ||
| ) | ||
| if TYPE_CHECKING: | ||
| from pyfiles_db.database_manager._db import _AsyncDB | ||
| BASE_PATH_STORAGE = Path(__file__).parent.parent.parent / "database" | ||
| class FilesDB: | ||
| """FilesDB, manager for DB.""" | ||
| _instance: ClassVar[Self | None] = None | ||
| def __new__(cls: type[Self]) -> Self: | ||
| """ | ||
| Create or return the existing singleton instance. | ||
| Returns | ||
| ------- | ||
| FilesDB | ||
| The existing or newly created singleton instance. | ||
| """ | ||
| if not cls._instance: | ||
| cls._instance = super().__new__(cls) | ||
| return cls._instance | ||
| def init_sync(self, | ||
| storage: Path | str | None = None, | ||
| *, | ||
| meta_file: str = "meta.json", | ||
| meta: dict[str, Any] | None = None, | ||
| ) -> _DBsync: | ||
| """Initialize a new synchronous database connection. | ||
| If a database is already loaded this returns a connection. If not, | ||
| creates the base meta information and returns a new connection. | ||
| Parameters | ||
| ---------- | ||
| storage : Path | str | None, optional | ||
| Path to database location, by default None | ||
| meta_file : str, optional | ||
| Name of meta file, by default "meta.json" | ||
| Returns | ||
| ------- | ||
| _DBsync | ||
| Synchronous database manager instance. | ||
| """ | ||
| storage = self._configure_database( | ||
| storage=storage, | ||
| meta_file=meta_file, | ||
| meta=meta) | ||
| return _DBsync(storage=storage, meta_file=self._meta_file) | ||
| def init_async(self, | ||
| storage: Path | str | None = None, | ||
| *, | ||
| meta_file: str = "meta.json", | ||
| meta: dict[str, Any] | None = None, | ||
| ) -> _AsyncDB: | ||
| """Initialize a new asynchronous database connection. | ||
| If a database is already loaded this returns a connection. If not, | ||
| creates the base meta information and returns a new async manager. | ||
| Parameters | ||
| ---------- | ||
| storage : Path | str | None, optional | ||
| Path to database location, by default None | ||
| meta_file : str, optional | ||
| Name of meta file, by default "meta.json" | ||
| Returns | ||
| ------- | ||
| _AsyncDB | ||
| Asynchronous database manager instance. | ||
| """ | ||
| storage = self._configure_database( | ||
| storage=storage, | ||
| meta_file=meta_file, | ||
| meta=meta) | ||
| return _DBasync(storage=storage, meta_file=self._meta_file) | ||
| def _configure_database( | ||
| self, | ||
| storage: str | Path | None, | ||
| meta_file: str, | ||
| meta: dict[str, Any] | None, | ||
| )-> str | Path: | ||
| if meta is None: | ||
| meta = {} | ||
| self._meta_file = meta_file | ||
| if storage is None: | ||
| storage = BASE_PATH_STORAGE | ||
| if self._check_storage(storage=storage): | ||
| self._create_base_meta_information(storage, meta=meta) | ||
| return storage | ||
| def _base_meta(self) -> dict[str, Any]: | ||
| """Return base meta information. | ||
| Returns | ||
| ------- | ||
| dict[str, Any] | ||
| Base meta information. | ||
| """ | ||
| return { | ||
| META.TABLES: [], | ||
| META.ENCRYPTDB: False, | ||
| META.TABLE_PREFIX: "TABLE_", | ||
| } | ||
| def _valid_key_value(self, key: str, value: Any) -> None: # noqa: ANN401 | ||
| """Validate data type for meta. | ||
| Parameters | ||
| ---------- | ||
| key : str | ||
| key of meta information | ||
| value : Any | ||
| value of meta information | ||
| Raises | ||
| ------ | ||
| TypeError | ||
| When data type is not valid | ||
| """ | ||
| match(key): | ||
| case META.TABLES: | ||
| if not isinstance(value, list): | ||
| raise TypeError | ||
| case META.ENCRYPTDB: | ||
| if not isinstance(value, bool): | ||
| raise TypeError | ||
| def _configure_meta(self, meta: dict[str, Any]) -> dict[str, Any]: | ||
| """Configure meta information. | ||
| Parameters | ||
| ---------- | ||
| meta : dict[str, Any] | ||
| raw meta information from user | ||
| Returns | ||
| ------- | ||
| dict[str, Any] | ||
| meta information | ||
| """ | ||
| new_meta = self._base_meta() | ||
| for key, value in meta.items(): | ||
| self._valid_key_value(key, value) | ||
| new_meta[key] = value | ||
| return new_meta | ||
| def _create_base_meta_information(self, | ||
| storage: str | Path, | ||
| meta: dict[str, Any]) -> None: | ||
| """Create base meta structure files. | ||
| Parameters | ||
| ---------- | ||
| storage : str | Path | ||
| path to database location | ||
| meta : dict[str, Any] | ||
| raw meta information from user | ||
| """ | ||
| meta = self._configure_meta(meta) | ||
| storage = Path(storage) | ||
| with Path.open(storage / self._meta_file, "w") as f: | ||
| json.dump(meta, f) | ||
| def _check_storage(self, storage: str | Path) -> bool: | ||
| """Check storage availability and create folder if needed. | ||
| Parameters | ||
| ---------- | ||
| storage : str | Path | ||
| Path to database location. | ||
| Returns | ||
| ------- | ||
| bool | ||
| True if the meta file already exists (database exists). | ||
| False if the meta file does not exist yet. | ||
| Raises | ||
| ------ | ||
| PathNotAvaibleError | ||
| If the path is not available. | ||
| NotADirectoryError | ||
| If the path exists but is not a directory. | ||
| """ | ||
| storage = Path(storage) | ||
| storage.mkdir(parents=True, exist_ok=True) | ||
| if not storage.exists(): | ||
| raise PathNotAvaibleError | ||
| if not storage.is_dir(): | ||
| raise NotADirectoryError | ||
| return not (storage / self._meta_file).exists() | ||
| # Copyright 2025 LangNeuron | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| """Utils.""" | ||
| from .infinity_number_generator import infinite_natural_numbers | ||
| __all__ = ["infinite_natural_numbers"] |
| # Copyright 2025 LangNeuron | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| """Infinity number generator.""" | ||
| from collections.abc import Generator | ||
| from typing import Any | ||
| def infinite_natural_numbers(start: int) -> Generator[Any, Any, Any]: | ||
| """Generate numbers.""" | ||
| number = start | ||
| while True: | ||
| yield number | ||
| number += 1 |
-201
| Apache License | ||
| Version 2.0, January 2004 | ||
| http://www.apache.org/licenses/ | ||
| TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | ||
| 1. Definitions. | ||
| "License" shall mean the terms and conditions for use, reproduction, | ||
| and distribution as defined by Sections 1 through 9 of this document. | ||
| "Licensor" shall mean the copyright owner or entity authorized by | ||
| the copyright owner that is granting the License. | ||
| "Legal Entity" shall mean the union of the acting entity and all | ||
| other entities that control, are controlled by, or are under common | ||
| control with that entity. For the purposes of this definition, | ||
| "control" means (i) the power, direct or indirect, to cause the | ||
| direction or management of such entity, whether by contract or | ||
| otherwise, or (ii) ownership of fifty percent (50%) or more of the | ||
| outstanding shares, or (iii) beneficial ownership of such entity. | ||
| "You" (or "Your") shall mean an individual or Legal Entity | ||
| exercising permissions granted by this License. | ||
| "Source" form shall mean the preferred form for making modifications, | ||
| including but not limited to software source code, documentation | ||
| source, and configuration files. | ||
| "Object" form shall mean any form resulting from mechanical | ||
| transformation or translation of a Source form, including but | ||
| not limited to compiled object code, generated documentation, | ||
| and conversions to other media types. | ||
| "Work" shall mean the work of authorship, whether in Source or | ||
| Object form, made available under the License, as indicated by a | ||
| copyright notice that is included in or attached to the work | ||
| (an example is provided in the Appendix below). | ||
| "Derivative Works" shall mean any work, whether in Source or Object | ||
| form, that is based on (or derived from) the Work and for which the | ||
| editorial revisions, annotations, elaborations, or other modifications | ||
| represent, as a whole, an original work of authorship. For the purposes | ||
| of this License, Derivative Works shall not include works that remain | ||
| separable from, or merely link (or bind by name) to the interfaces of, | ||
| the Work and Derivative Works thereof. | ||
| "Contribution" shall mean any work of authorship, including | ||
| the original version of the Work and any modifications or additions | ||
| to that Work or Derivative Works thereof, that is intentionally | ||
| submitted to Licensor for inclusion in the Work by the copyright owner | ||
| or by an individual or Legal Entity authorized to submit on behalf of | ||
| the copyright owner. For the purposes of this definition, "submitted" | ||
| means any form of electronic, verbal, or written communication sent | ||
| to the Licensor or its representatives, including but not limited to | ||
| communication on electronic mailing lists, source code control systems, | ||
| and issue tracking systems that are managed by, or on behalf of, the | ||
| Licensor for the purpose of discussing and improving the Work, but | ||
| excluding communication that is conspicuously marked or otherwise | ||
| designated in writing by the copyright owner as "Not a Contribution." | ||
| "Contributor" shall mean Licensor and any individual or Legal Entity | ||
| on behalf of whom a Contribution has been received by Licensor and | ||
| subsequently incorporated within the Work. | ||
| 2. Grant of Copyright License. Subject to the terms and conditions of | ||
| this License, each Contributor hereby grants to You a perpetual, | ||
| worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||
| copyright license to reproduce, prepare Derivative Works of, | ||
| publicly display, publicly perform, sublicense, and distribute the | ||
| Work and such Derivative Works in Source or Object form. | ||
| 3. Grant of Patent License. Subject to the terms and conditions of | ||
| this License, each Contributor hereby grants to You a perpetual, | ||
| worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||
| (except as stated in this section) patent license to make, have made, | ||
| use, offer to sell, sell, import, and otherwise transfer the Work, | ||
| where such license applies only to those patent claims licensable | ||
| by such Contributor that are necessarily infringed by their | ||
| Contribution(s) alone or by combination of their Contribution(s) | ||
| with the Work to which such Contribution(s) was submitted. If You | ||
| institute patent litigation against any entity (including a | ||
| cross-claim or counterclaim in a lawsuit) alleging that the Work | ||
| or a Contribution incorporated within the Work constitutes direct | ||
| or contributory patent infringement, then any patent licenses | ||
| granted to You under this License for that Work shall terminate | ||
| as of the date such litigation is filed. | ||
| 4. Redistribution. You may reproduce and distribute copies of the | ||
| Work or Derivative Works thereof in any medium, with or without | ||
| modifications, and in Source or Object form, provided that You | ||
| meet the following conditions: | ||
| (a) You must give any other recipients of the Work or | ||
| Derivative Works a copy of this License; and | ||
| (b) You must cause any modified files to carry prominent notices | ||
| stating that You changed the files; and | ||
| (c) You must retain, in the Source form of any Derivative Works | ||
| that You distribute, all copyright, patent, trademark, and | ||
| attribution notices from the Source form of the Work, | ||
| excluding those notices that do not pertain to any part of | ||
| the Derivative Works; and | ||
| (d) If the Work includes a "NOTICE" text file as part of its | ||
| distribution, then any Derivative Works that You distribute must | ||
| include a readable copy of the attribution notices contained | ||
| within such NOTICE file, excluding those notices that do not | ||
| pertain to any part of the Derivative Works, in at least one | ||
| of the following places: within a NOTICE text file distributed | ||
| as part of the Derivative Works; within the Source form or | ||
| documentation, if provided along with the Derivative Works; or, | ||
| within a display generated by the Derivative Works, if and | ||
| wherever such third-party notices normally appear. The contents | ||
| of the NOTICE file are for informational purposes only and | ||
| do not modify the License. You may add Your own attribution | ||
| notices within Derivative Works that You distribute, alongside | ||
| or as an addendum to the NOTICE text from the Work, provided | ||
| that such additional attribution notices cannot be construed | ||
| as modifying the License. | ||
| You may add Your own copyright statement to Your modifications and | ||
| may provide additional or different license terms and conditions | ||
| for use, reproduction, or distribution of Your modifications, or | ||
| for any such Derivative Works as a whole, provided Your use, | ||
| reproduction, and distribution of the Work otherwise complies with | ||
| the conditions stated in this License. | ||
| 5. Submission of Contributions. Unless You explicitly state otherwise, | ||
| any Contribution intentionally submitted for inclusion in the Work | ||
| by You to the Licensor shall be under the terms and conditions of | ||
| this License, without any additional terms or conditions. | ||
| Notwithstanding the above, nothing herein shall supersede or modify | ||
| the terms of any separate license agreement you may have executed | ||
| with Licensor regarding such Contributions. | ||
| 6. Trademarks. This License does not grant permission to use the trade | ||
| names, trademarks, service marks, or product names of the Licensor, | ||
| except as required for reasonable and customary use in describing the | ||
| origin of the Work and reproducing the content of the NOTICE file. | ||
| 7. Disclaimer of Warranty. Unless required by applicable law or | ||
| agreed to in writing, Licensor provides the Work (and each | ||
| Contributor provides its Contributions) on an "AS IS" BASIS, | ||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | ||
| implied, including, without limitation, any warranties or conditions | ||
| of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | ||
| PARTICULAR PURPOSE. You are solely responsible for determining the | ||
| appropriateness of using or redistributing the Work and assume any | ||
| risks associated with Your exercise of permissions under this License. | ||
| 8. Limitation of Liability. In no event and under no legal theory, | ||
| whether in tort (including negligence), contract, or otherwise, | ||
| unless required by applicable law (such as deliberate and grossly | ||
| negligent acts) or agreed to in writing, shall any Contributor be | ||
| liable to You for damages, including any direct, indirect, special, | ||
| incidental, or consequential damages of any character arising as a | ||
| result of this License or out of the use or inability to use the | ||
| Work (including but not limited to damages for loss of goodwill, | ||
| work stoppage, computer failure or malfunction, or any and all | ||
| other commercial damages or losses), even if such Contributor | ||
| has been advised of the possibility of such damages. | ||
| 9. Accepting Warranty or Additional Liability. While redistributing | ||
| the Work or Derivative Works thereof, You may choose to offer, | ||
| and charge a fee for, acceptance of support, warranty, indemnity, | ||
| or other liability obligations and/or rights consistent with this | ||
| License. However, in accepting such obligations, You may act only | ||
| on Your own behalf and on Your sole responsibility, not on behalf | ||
| of any other Contributor, and only if You agree to indemnify, | ||
| defend, and hold each Contributor harmless for any liability | ||
| incurred by, or claims asserted against, such Contributor by reason | ||
| of your accepting any such warranty or additional liability. | ||
| END OF TERMS AND CONDITIONS | ||
| APPENDIX: How to apply the Apache License to your work. | ||
| To apply the Apache License to your work, attach the following | ||
| boilerplate notice, with the fields enclosed by brackets "[]" | ||
| replaced with your own identifying information. (Don't include | ||
| the brackets!) The text should be enclosed in the appropriate | ||
| comment syntax for the file format. We also recommend that a | ||
| file or class name and description of purpose be included on the | ||
| same "printed page" as the copyright notice for easier | ||
| identification within third-party archives. | ||
| Copyright 2025 LangNeuron | ||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||
| you may not use this file except in compliance with the License. | ||
| You may obtain a copy of the License at | ||
| http://www.apache.org/licenses/LICENSE-2.0 | ||
| Unless required by applicable law or agreed to in writing, software | ||
| distributed under the License is distributed on an "AS IS" BASIS, | ||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| See the License for the specific language governing permissions and | ||
| limitations under the License. |
-128
| Metadata-Version: 2.4 | ||
| Name: python-files-db | ||
| Version: 0.0.1a4 | ||
| Summary: Lightweight file-system database for Python projects. | ||
| License-File: LICENSE | ||
| Keywords: database,filesystem,json,embedded,fastapi | ||
| Author: Anton Golubchikov | ||
| Author-email: anton1programmist@gmail.com | ||
| Requires-Python: >=3.12 | ||
| Classifier: Development Status :: 3 - Alpha | ||
| Classifier: Intended Audience :: Developers | ||
| Classifier: Programming Language :: Python :: 3 | ||
| Classifier: Operating System :: OS Independent | ||
| Requires-Dist: aiofiles (>=25.1.0,<26.0.0) | ||
| Project-URL: Bug Tracker, https://github.com/LangNeuron/pyfiles_db/issues | ||
| Project-URL: Homepage, https://github.com/LangNeuron/pyfiles_db | ||
| Description-Content-Type: text/markdown | ||
| # pyfiles_db | ||
| Lightweight file-system database for Python projects | ||
| ``` | ||
| PRE RELISE FIRST VERSION OF PYFILES_D | ||
| ``` | ||
| ## About | ||
| `pyfiles_db` is a lightweight and fast library that allows using the file system as a database for Python projects. It is minimalistic, requires no full-fledged DBMS, and is ideal for small to medium projects where simplicity and speed are priorities, and when system resources are a concern. It is built on top of `aiofiles` which allows you to use the file system as a database without any server required. Also can with synchronous mode. | ||
| ## Features | ||
| - Store data directly in folders and files (no server required) | ||
| - Simple API — quick to start | ||
| - Supports basic CRUD operations: Create, Read, Update, Delete | ||
| - Minimal external dependencies | ||
| - Designed for easy integration and flexibility | ||
| ## Installation | ||
| ```bash | ||
| pip install python-files-db | ||
| ``` | ||
| ## Quick Start | ||
| ```python | ||
| from pyfiles_db import FilesDB | ||
| file_db = FilesDB() | ||
| db = file_db.init_sync() # Or file_db.init_async() for async mode | ||
| # storage - path to database location, if is None use default path. | ||
| db.create_table( | ||
| "users", | ||
| columns={ | ||
| "id": "INT", | ||
| "name": "TEXT", | ||
| "age": "INT", | ||
| }, | ||
| id_generator="id", # required unique value of table, if None use generator for auto increment. | ||
| ) | ||
| db.new_data(table_name="users", data={ | ||
| "id": 1, | ||
| "name": "Anton", | ||
| "age": 17, | ||
| }) | ||
| db.new_data(table_name="users", data={ | ||
| "id": 2, | ||
| "name": "Alex", | ||
| "age": 17, | ||
| }) | ||
| user_id_1 = db.find("users", "id == 1") | ||
| # return [{"1": {"id": 1, "name": "Anton", "age": 17}}], 1 is file_id | ||
| user_id_2 = db.find("users", "id == 2") | ||
| # return [{"2": {"id": 2, "name": "Alex", "age": 17}}], 2 is file_id | ||
| users_age_17 = db.find("users", "age == 17") | ||
| # return [ | ||
| # {"1": {"id": 1, "name": "Anton", "age": 17}}, # 1 is file_id | ||
| # {"2": {"id": 2, "name": "Alex", "age": 17}, # 2 is file_id | ||
| # ] | ||
| ``` | ||
| Or async version: | ||
| ```python | ||
| from pyfiles_db import FilesDB | ||
| import asyncio | ||
| async def main(): | ||
| file_db = FilesDB() | ||
| db = file_db.init_async("path/to/folder") | ||
| await db.create_table(...) | ||
| await db.new_data(...) | ||
| res = await db.find(...) | ||
| asyncio.run(main()) | ||
| ``` | ||
| ## Use Cases | ||
| - Quick startups, prototypes, MVPs | ||
| - Lightweight web applications, scripts, utilities | ||
| - Projects not requiring a heavy DBMS | ||
| - Personal projects and data analysis tools | ||
| - low RAM and processor characteristics | ||
| ## Best Practices & Limitations | ||
| - Not designed for high-load systems with thousands of requests per second | ||
| - Maintain folder structure carefully to avoid naming collisions | ||
| - As this uses the file system, consider transaction/concurrency limitations | ||
| - Regularly back up the path folder | ||
| ## Contribution | ||
| Feedback, issues, and pull requests are welcome! | ||
| Follow the contribution guidelines if available. | ||
| Ensure your code is tested (tests in /tests/) and document changes. | ||
| ## License | ||
| This project is licensed under the Apache-2.0 License. See [LICENSE](https://github.com/LangNeuron/pyfiles_db/blob/main/LICENSE) for details. | ||
| ## Other | ||
| ### [CODE_OF_CONDUCT](https://github.com/LangNeuron/pyfiles_db/blob/main/CODE_OF_CONDUCT.md) | ||
| ### [CONTRIBUTING](https://github.com/LangNeuron/pyfiles_db/blob/main/CONTRIBUTING.md) | ||
| # Copyright 2025 LangNeuron | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| """Init library.""" | ||
| from .files_db import FilesDB | ||
| __all__ = ["FilesDB"] |
| # Copyright 2025 LangNeuron | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| """Init database managers.""" | ||
| from ._db import _DB | ||
| from .async_db import _DBasync | ||
| from .meta import META | ||
| from .sync_db import _DBsync | ||
| __all__ = ["META", "_DB", "_DBasync", "_DBsync"] |
| # Copyright 2025 LangNeuron | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| """Abstrct database manager.""" | ||
| from abc import ABC, abstractmethod | ||
| from collections.abc import Coroutine | ||
| from pathlib import Path | ||
| from typing import Any | ||
| class _DB(ABC): | ||
| @abstractmethod | ||
| def __init__(self, storage: str | Path, meta_file: str) -> None: | ||
| """Init database. | ||
| Parameters | ||
| ---------- | ||
| storage : str | Path | ||
| path to db location | ||
| meta_file : str | ||
| name of meta file | ||
| """ | ||
| @abstractmethod | ||
| def create_table(self, table_name: str, | ||
| columns: dict[str, str], | ||
| id_generator: str | int | None = None, | ||
| ) -> None | Coroutine[Any, Any, None]: | ||
| """Create a new table. | ||
| Parameters | ||
| ---------- | ||
| table_name : str | ||
| name of table | ||
| columns : dict[str, str] | ||
| columns with data type | ||
| id_generator : str, None | ||
| default None | ||
| str is name of column data when need use how nameing of file | ||
| None use simple id generator (increment, not recominded) | ||
| """ | ||
| @abstractmethod | ||
| def new_data(self, table_name: str, data: dict[str, Any]) -> None: | ||
| """Add new data to database. | ||
| Parameters | ||
| ---------- | ||
| tabel : str | ||
| name of data table | ||
| data : dict[str, Any] | ||
| information when need save | ||
| """ | ||
| @abstractmethod | ||
| def find(self, table_name: str, condition: str) -> list[dict[str, Any]]: | ||
| """Find information in database. | ||
| Parameters | ||
| ---------- | ||
| table_name : str | ||
| name of table | ||
| condition : str | ||
| maybe is "id == 1" | ||
| Returns | ||
| ------- | ||
| list[dict[str, Any]] | ||
| all data in table | ||
| """ | ||
| @abstractmethod | ||
| def update(self, | ||
| table_name: str, | ||
| file_id: str, | ||
| new_data: dict[str, Any], | ||
| ) -> None: | ||
| """Update data with file_id. | ||
| Get file_id from find method. | ||
| Parameters | ||
| ---------- | ||
| file_id : str | ||
| unique file name | ||
| new_data : dict[str, Any] | ||
| new data when need save | ||
| """ | ||
| @abstractmethod | ||
| def delete(self, | ||
| table_name: str, | ||
| file_id: str, | ||
| ) -> None: | ||
| """Delete data with file_id. | ||
| Parameters | ||
| ---------- | ||
| table_name : str | ||
| name of table db | ||
| file_id : str | ||
| name of file in table | ||
| """ | ||
| class _AsyncDB(ABC): | ||
| @abstractmethod | ||
| def __init__(self, storage: str | Path, meta_file: str) -> None: | ||
| """Initialize the asynchronous database manager. | ||
| Parameters | ||
| ---------- | ||
| storage : str | Path | ||
| Path to database location. | ||
| meta_file : str | ||
| Name of meta file. | ||
| """ | ||
| @abstractmethod | ||
| async def create_table(self, table_name: str, | ||
| columns: dict[str, str], | ||
| id_generator: str | int | None = None, | ||
| ) -> None: | ||
| """Create a new table (async). | ||
| Parameters | ||
| ---------- | ||
| table_name : str | ||
| Name of the table. | ||
| columns : dict[str, str] | ||
| Columns mapping to their data types. | ||
| id_generator : str | int | None | ||
| If a string, this is the column name used as file identifier. | ||
| If None, an integer auto-increment generator is used. | ||
| """ | ||
| @abstractmethod | ||
| async def new_data(self, table_name: str, data: dict[str, Any]) -> None: | ||
| """Add new data to the database (async). | ||
| Parameters | ||
| ---------- | ||
| table_name : str | ||
| Name of the table. | ||
| data : dict[str, Any] | ||
| Record to save. | ||
| """ | ||
| @abstractmethod | ||
| async def find(self, | ||
| table_name: str, | ||
| condition: str, | ||
| ) -> list[dict[str, Any]]: | ||
| """Find information in database. | ||
| Parameters | ||
| ---------- | ||
| table_name : str | ||
| name of table | ||
| condition : str | ||
| maybe is "id == 1" | ||
| Returns | ||
| ------- | ||
| list[dict[str, Any]] | ||
| all data in table | ||
| """ | ||
| @abstractmethod | ||
| async def update(self, | ||
| table_name: str, | ||
| file_id: str, | ||
| new_data: dict[str, Any], | ||
| ) -> None: | ||
| """Update data with file_id. | ||
| Get file_id from find method. | ||
| Parameters | ||
| ---------- | ||
| file_id : str | ||
| unique file name | ||
| new_data : dict[str, Any] | ||
| new data when need save | ||
| """ | ||
| @abstractmethod | ||
| async def delete(self, | ||
| table_name: str, | ||
| file_id: str, | ||
| ) -> None: | ||
| """Delete data with file_id. | ||
| Parameters | ||
| ---------- | ||
| table_name : str | ||
| name of table db | ||
| file_id : str | ||
| name of file in table | ||
| """ |
| # Copyright 2025 LangNeuron | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| """Async database manager.""" | ||
| import json | ||
| from pathlib import Path | ||
| from typing import TYPE_CHECKING, Any | ||
| import aiofiles | ||
| from pyfiles_db.database_manager._db import _AsyncDB | ||
| from pyfiles_db.database_manager.meta import META | ||
| from pyfiles_db.errors import ( | ||
| DataIsUncorrectError, | ||
| NotFoundColumnError, | ||
| NotFoundTableError, | ||
| TableAlreadyAvaibleError, | ||
| UnknownDataTypeError, | ||
| ) | ||
| from pyfiles_db.utils import infinite_natural_numbers | ||
| if TYPE_CHECKING: | ||
| from collections.abc import Generator | ||
| class _DBasync(_AsyncDB): | ||
| def __init__(self, storage: str | Path, meta_file: str) -> None: | ||
| """Initialize the asynchronous database manager. | ||
| Parameters | ||
| ---------- | ||
| storage : str | Path | ||
| Path to the database location. | ||
| meta_file : str | ||
| Name of the meta file. | ||
| """ | ||
| self._storage = Path(storage) | ||
| self._meta_file = meta_file | ||
| self._id_generators: dict[str, Generator[Any, Any, Any]] = {} | ||
| self._load_meta() | ||
| def _load_meta(self) -> None: | ||
| """Load meta information from file.""" | ||
| with Path.open(self._storage / self._meta_file, "r") as f: | ||
| self._meta = json.load(f) | ||
| async def create_table( | ||
| self, table_name: str, | ||
| columns: dict[str, Any], | ||
| id_generator: str | int | None = None, | ||
| ) -> None: | ||
| """Create a table (async). | ||
| Parameters | ||
| ---------- | ||
| table_name : str | ||
| Name of the table. | ||
| columns : dict[str, str] | ||
| Columns mapping to their data types. | ||
| id_generator : str | None | ||
| Generator for file names. Default None. | ||
| Raises | ||
| ------ | ||
| TableAlreadyAvaibleError | ||
| If the table already exists. | ||
| """ | ||
| # Table. columns is maybe {"USER_ID": "INT", "NAME": "TEXT"} | ||
| table = self._meta[META.TABLE_PREFIX] + table_name | ||
| if table in self._meta[META.TABLES]: | ||
| raise TableAlreadyAvaibleError | ||
| if id_generator is None: | ||
| id_generator = 0 | ||
| self._id_generators[table] = infinite_natural_numbers(id_generator) | ||
| await self._mkdir_for_table(table) | ||
| self._meta[META.TABLES].append(table) | ||
| self._meta[table] = { | ||
| META.COLUMNS: columns, | ||
| META.GENERATOR: id_generator} | ||
| await self._update_meta() | ||
| async def _update_meta(self) -> None: | ||
| """Update meta file.""" | ||
| async with aiofiles.open( | ||
| self._storage / self._meta_file, | ||
| mode="w", | ||
| ) as f: | ||
| await f.write(json.dumps(self._meta)) | ||
| async def _mkdir_for_table(self, table: str | Path) -> None: | ||
| """Create the on-disk folder and index file for a table. | ||
| Parameters | ||
| ---------- | ||
| table : str | Path | ||
| Name of the table folder. | ||
| """ | ||
| (self._storage / table).mkdir(parents=False, exist_ok=True) | ||
| async with aiofiles.open( | ||
| self._storage / table / ".json", | ||
| mode="w", | ||
| ) as f: | ||
| await f.write(json.dumps({META.FILE_IDS: []})) | ||
| async def new_data(self, table_name: str, data: dict[str, Any]) -> None: | ||
| """Add new data to the table (async). | ||
| Parameters | ||
| ---------- | ||
| table_name : str | ||
| Name of the table. | ||
| data : dict[str, Any] | ||
| The record to save. | ||
| """ | ||
| table_name = self._meta[META.TABLE_PREFIX] + table_name | ||
| if not self._check_table(table_name): | ||
| raise NotFoundTableError(table_name=table_name) | ||
| if not self._check_data(self._meta[table_name][META.COLUMNS], data): | ||
| raise DataIsUncorrectError(data=data) | ||
| file_name = "" | ||
| if (self._meta[table_name][META.GENERATOR] is None or | ||
| isinstance(self._meta[table_name][META.GENERATOR], int)): | ||
| if self._id_generators.get(table_name) is None: | ||
| self._id_generators[table_name] = infinite_natural_numbers( | ||
| self._meta[table_name][META.GENERATOR]) | ||
| file_name = next(self._id_generators[table_name]) | ||
| self._meta[table_name][META.GENERATOR] += 1 | ||
| await self._update_meta() | ||
| else: | ||
| file_name = data[self._meta[table_name][META.GENERATOR]] | ||
| async with aiofiles.open( | ||
| self._storage / table_name / f"{file_name}.json", | ||
| mode="w") as f: | ||
| await f.write(json.dumps(data)) | ||
| async with aiofiles.open( | ||
| self._storage / table_name / ".json") as f: | ||
| content = await f.read() | ||
| data = json.loads(content) | ||
| data[META.FILE_IDS].append(file_name) | ||
| async with aiofiles.open( | ||
| self._storage / table_name / ".json", mode="w") as f: | ||
| await f.write(json.dumps(data)) | ||
| def _check_table(self, table: str) -> bool: | ||
| """Check table for exists. | ||
| Parameters | ||
| ---------- | ||
| table : str | ||
| name of table | ||
| Returns | ||
| ------- | ||
| bool | ||
| exist table | ||
| """ | ||
| return table in self._meta[META.TABLES] | ||
| def _check_data(self, columns: dict[str, str], | ||
| data: dict[str, Any]) -> bool: | ||
| """Check type data. | ||
| Parameters | ||
| ---------- | ||
| columns : dict[str, str] | ||
| col of table | ||
| data : dict[str, Any] | ||
| new information | ||
| Returns | ||
| ------- | ||
| bool | ||
| Data is correct | ||
| """ | ||
| for key, val in data.items(): | ||
| if (self._change_type(val, columns[key]) != val): | ||
| return False | ||
| return True | ||
| def _change_type(self, value: str, column_type: str) -> Any: # noqa: ANN401 | ||
| """Change data type. | ||
| Parameters | ||
| ---------- | ||
| value : str | ||
| value | ||
| column_type : str | ||
| data type | ||
| Returns | ||
| ------- | ||
| Any | ||
| correct data type | ||
| Raises | ||
| ------ | ||
| ValueError | ||
| if column_type is unknown | ||
| """ | ||
| match column_type: | ||
| case "INT": | ||
| return int(value) | ||
| case "TEXT": | ||
| return str(value) | ||
| case _: | ||
| raise UnknownDataTypeError | ||
| async def find(self, | ||
| table_name: str, | ||
| condition: str, | ||
| ) -> list[dict[str, Any]]: | ||
| """Find information in table. | ||
| Parameters | ||
| ---------- | ||
| table_name : str | ||
| name of table | ||
| condition : str | ||
| condition, maybe "id == 5" | ||
| Returns | ||
| ------- | ||
| list[dict[str, Any]] | ||
| all data when find condition | ||
| Raises | ||
| ------ | ||
| ValueError | ||
| Table not found error | ||
| ValueError | ||
| Column not found error | ||
| """ | ||
| table_name = self._meta[META.TABLE_PREFIX] + table_name | ||
| if not self._check_table(table_name): | ||
| raise NotFoundTableError(table_name=table_name) | ||
| column_name, value = condition.replace(" ", "").split("==") | ||
| if not self._check_column_in_table(table_name, column_name): | ||
| raise NotFoundColumnError(column_name=column_name, | ||
| table_name=table_name) | ||
| value = self._change_type(value, | ||
| self._meta[table_name][META.COLUMNS][column_name]) | ||
| if self._meta[table_name][META.GENERATOR] == column_name: | ||
| async with aiofiles.open( | ||
| self._storage / table_name / f"{value}.json") as f: | ||
| content = await f.read() | ||
| data = json.loads(content) | ||
| if isinstance(data, dict): | ||
| return [{str(value): data}] | ||
| return [] | ||
| async with aiofiles.open( | ||
| self._storage / table_name / ".json") as f: | ||
| content = await f.read() | ||
| data = json.loads(content) | ||
| names = data[META.FILE_IDS] | ||
| result: list[dict[str, Any]] = [] | ||
| for name in names: | ||
| async with aiofiles.open( | ||
| self._storage / table_name / f"{name}.json") as f: | ||
| content = await f.read() | ||
| d = json.loads(content) | ||
| if d[column_name] == value and isinstance(d, dict): | ||
| result.append({str(name): d}) | ||
| return result | ||
| def _check_column_in_table(self, table_name: str, column_name: str) -> bool: | ||
| """Check column in table on exist. | ||
| Parameters | ||
| ---------- | ||
| table_name : str | ||
| name of table | ||
| column_name : str | ||
| name of column | ||
| Returns | ||
| ------- | ||
| bool | ||
| exist column | ||
| """ | ||
| return column_name in self._meta[table_name][META.COLUMNS] | ||
| async def update(self, | ||
| table_name: str, | ||
| file_id: str, | ||
| new_data: dict[str, Any], | ||
| ) -> None: | ||
| """Update data with file_id. | ||
| Get file_id from find method. | ||
| Parameters | ||
| ---------- | ||
| file_id : str | ||
| unique file name | ||
| new_data : dict[str, Any] | ||
| new data when need save | ||
| """ | ||
| table_name = self._meta[META.TABLE_PREFIX] + table_name | ||
| async with aiofiles.open( | ||
| self._storage / table_name / f"{file_id}.json", | ||
| mode="w") as f: | ||
| await f.write(json.dumps(new_data)) | ||
| async def delete(self, | ||
| table_name: str, | ||
| file_id: str, | ||
| ) -> None: | ||
| """Delete data with file_id. | ||
| Parameters | ||
| ---------- | ||
| table_name : str | ||
| name of table db | ||
| file_id : str | ||
| name of file in table | ||
| """ | ||
| table_name = self._meta[META.TABLE_PREFIX] + table_name | ||
| if not (self._storage / table_name / f"{file_id}.json").exists(): | ||
| raise FileNotFoundError | ||
| (self._storage / table_name / f"{file_id}.json").unlink() |
| # Copyright 2025 LangNeuron | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| """Sync database manager.""" | ||
| from dataclasses import dataclass | ||
| @dataclass | ||
| class META: | ||
| """Meta data for the database.""" | ||
| TABLES: str = "TABLES" | ||
| ENCRYPTDB: str = "ENCRYPTDB" | ||
| COLUMNS: str = "COLUMNS" | ||
| TABLE_PREFIX: str = "TABLE_PREFIX" | ||
| GENERATOR: str = "GENERATOR" | ||
| FILE_IDS: str = "FILE_IDS" |
| # Copyright 2025 LangNeuron | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| """Sync database manager.""" | ||
| import json | ||
| from pathlib import Path | ||
| from typing import TYPE_CHECKING, Any | ||
| from pyfiles_db.database_manager._db import _DB | ||
| from pyfiles_db.database_manager.meta import META | ||
| from pyfiles_db.errors import ( | ||
| DataIsUncorrectError, | ||
| NotFoundColumnError, | ||
| NotFoundTableError, | ||
| TableAlreadyAvaibleError, | ||
| UnknownDataTypeError, | ||
| ) | ||
| from pyfiles_db.utils import infinite_natural_numbers | ||
| if TYPE_CHECKING: | ||
| from collections.abc import Generator | ||
| class _DBsync(_DB): | ||
| def __init__(self, storage: str | Path, meta_file: str) -> None: | ||
| """Initialize the synchronous database manager. | ||
| Parameters | ||
| ---------- | ||
| storage : str | Path | ||
| Path to the database location. | ||
| meta_file : str | ||
| Name of the meta file. | ||
| """ | ||
| self._storage = Path(storage) | ||
| self._meta_file = meta_file | ||
| self._load_meta() | ||
| self._id_generators: dict[str, Generator[Any, Any, Any]] = {} | ||
| def _load_meta(self) -> None: | ||
| """Load meta information from file.""" | ||
| with Path.open(self._storage / self._meta_file, "r") as f: | ||
| self._meta = json.load(f) | ||
| def create_table(self, table_name: str, columns: dict[str, str], | ||
| id_generator: str | int | None = None) -> None: | ||
| """Create a table (sync). | ||
| Parameters | ||
| ---------- | ||
| table_name : str | ||
| Name of the table. | ||
| columns : dict[str, str] | ||
| Columns mapping to their data types. | ||
| id_generator : str | None | ||
| Generator for file names. Default None. | ||
| Raises | ||
| ------ | ||
| TableAlreadyAvaibleError | ||
| If the table already exists. | ||
| """ | ||
| # Table. columns is maybe {"USER_ID": "INT", "NAME": "TEXT"} | ||
| table = self._meta[META.TABLE_PREFIX] + table_name | ||
| if table in self._meta[META.TABLES]: | ||
| raise TableAlreadyAvaibleError | ||
| if id_generator is None: | ||
| id_generator = 0 | ||
| self._id_generators[table] = infinite_natural_numbers(id_generator) | ||
| self._mkdir_for_table(table) | ||
| self._meta[META.TABLES].append(table) | ||
| self._meta[table] = { | ||
| META.COLUMNS: columns, | ||
| META.GENERATOR: id_generator} | ||
| self._update_meta() | ||
| def _update_meta(self) -> None: | ||
| """Update meta file.""" | ||
| with Path.open(self._storage / self._meta_file, mode="w") as f: | ||
| json.dump(self._meta, f) | ||
| def _mkdir_for_table(self, table: str | Path) -> None: | ||
| """Create the on-disk folder and index file for a table. | ||
| Parameters | ||
| ---------- | ||
| table : str | Path | ||
| Name of the table folder. | ||
| """ | ||
| (self._storage / table).mkdir(parents=False, exist_ok=True) | ||
| with Path.open(self._storage / table / ".json", mode="w") as f: | ||
| json.dump({META.FILE_IDS: []}, f) | ||
| def new_data(self, table_name: str, data: dict[str, Any]) -> None: | ||
| """Save new data to the table. | ||
| Parameters | ||
| ---------- | ||
| table_name : str | ||
| Name of the table. | ||
| data : dict[str, Any] | ||
| Record to save. | ||
| """ | ||
| table_name = self._meta[META.TABLE_PREFIX] + table_name | ||
| if not self._check_table(table_name): | ||
| raise NotFoundTableError(table_name=table_name) | ||
| if not self._check_data(self._meta[table_name][META.COLUMNS], data): | ||
| raise DataIsUncorrectError(data=data) | ||
| file_name = "" | ||
| if (self._meta[table_name][META.GENERATOR] is None or | ||
| isinstance(self._meta[table_name][META.GENERATOR], int)): | ||
| if self._id_generators.get(table_name) is None: | ||
| self._id_generators[table_name] = infinite_natural_numbers( | ||
| self._meta[table_name][META.GENERATOR]) | ||
| file_name = next(self._id_generators[table_name]) | ||
| self._meta[table_name][META.GENERATOR] += 1 | ||
| self._update_meta() | ||
| else: | ||
| file_name = data[self._meta[table_name][META.GENERATOR]] | ||
| with Path.open( | ||
| self._storage / table_name / f"{file_name}.json", | ||
| mode="w") as f: | ||
| json.dump(data, f) | ||
| with Path.open(self._storage / table_name / ".json", mode="r") as f: | ||
| data = json.load(f) | ||
| data[META.FILE_IDS].append(file_name) | ||
| with Path.open(self._storage / table_name / ".json", mode="w") as f: | ||
| json.dump(data, f) | ||
| def _check_table(self, table: str) -> bool: | ||
| """Check whether a table exists. | ||
| Parameters | ||
| ---------- | ||
| table : str | ||
| Name of the table. | ||
| Returns | ||
| ------- | ||
| bool | ||
| True if the table exists. | ||
| """ | ||
| return table in self._meta[META.TABLES] | ||
| def _check_data(self, columns: dict[str, str], | ||
| data: dict[str, Any]) -> bool: | ||
| """Validate data types for a record against table columns. | ||
| Parameters | ||
| ---------- | ||
| columns : dict[str, str] | ||
| Column definitions for the table. | ||
| data : dict[str, Any] | ||
| Record to validate. | ||
| Returns | ||
| ------- | ||
| bool | ||
| True if the record matches the declared column types. | ||
| """ | ||
| for key, val in data.items(): | ||
| if (self._change_type(val, columns[key]) != val): | ||
| return False | ||
| return True | ||
| def find(self, | ||
| table_name: str, | ||
| condition: str, | ||
| ) -> list[dict[str, Any]]: | ||
| """Find records in a table matching a simple condition. | ||
| Parameters | ||
| ---------- | ||
| table_name : str | ||
| Name of the table. | ||
| condition : str | ||
| Condition string, e.g. "id == 5". | ||
| Returns | ||
| ------- | ||
| list[dict[str, Any]] | ||
| Records that match the condition. | ||
| Raises | ||
| ------ | ||
| ValueError | ||
| Table not found or column not found. | ||
| """ | ||
| table_name = self._meta[META.TABLE_PREFIX] + table_name | ||
| if not self._check_table(table_name): | ||
| raise NotFoundTableError(table_name=table_name) | ||
| column_name, value = condition.replace(" ", "").split("==") | ||
| if not self._check_column_in_table(table_name, column_name): | ||
| raise NotFoundColumnError(column_name=column_name, | ||
| table_name=table_name) | ||
| value = self._change_type(value, | ||
| self._meta[table_name][META.COLUMNS][column_name]) | ||
| if self._meta[table_name][META.GENERATOR] == column_name: | ||
| with Path.open( | ||
| self._storage / table_name / f"{value}.json", | ||
| mode="r") as f: | ||
| data = json.load(f) | ||
| if isinstance(data, dict): | ||
| return [{str(value): data}] | ||
| return [] | ||
| with Path.open( | ||
| self._storage / table_name / ".json",mode="r") as f: | ||
| data = json.load(f) | ||
| names = data[META.FILE_IDS] | ||
| result: list[dict[str, Any]] = [] | ||
| for name in names: | ||
| with Path.open( | ||
| self._storage / table_name / f"{name}.json", | ||
| mode="r") as f: | ||
| d = json.load(f) | ||
| if d[column_name] == value and isinstance(d, dict): | ||
| result.append({str(name): d}) | ||
| return result | ||
| def _check_column_in_table(self, table_name: str, column_name: str) -> bool: | ||
| """Check column in table on exist. | ||
| Parameters | ||
| ---------- | ||
| table_name : str | ||
| name of table | ||
| column_name : str | ||
| name of column | ||
| Returns | ||
| ------- | ||
| bool | ||
| exist column | ||
| """ | ||
| return column_name in self._meta[table_name][META.COLUMNS] | ||
| def _change_type(self, value: str, column_type: str) -> Any: # noqa: ANN401 | ||
| """Change data type. | ||
| Parameters | ||
| ---------- | ||
| value : str | ||
| value | ||
| column_type : str | ||
| data type | ||
| Returns | ||
| ------- | ||
| Any | ||
| correct data type | ||
| Raises | ||
| ------ | ||
| ValueError | ||
| if column_type is unknown | ||
| """ | ||
| match column_type: | ||
| case "INT": | ||
| return int(value) | ||
| case "TEXT": | ||
| return str(value) | ||
| case _: | ||
| raise UnknownDataTypeError | ||
| def update(self, | ||
| table_name: str, | ||
| file_id: str, | ||
| new_data: dict[str, Any], | ||
| ) -> None: | ||
| """Update data with file_id. | ||
| Get file_id from find method. | ||
| Parameters | ||
| ---------- | ||
| file_id : str | ||
| unique file name | ||
| new_data : dict[str, Any] | ||
| new data when need save | ||
| """ | ||
| table_name = self._meta[META.TABLE_PREFIX] + table_name | ||
| with Path.open( | ||
| self._storage / table_name / f"{file_id}.json", | ||
| mode="w") as f: | ||
| json.dump(new_data, f) | ||
| def delete(self, | ||
| table_name: str, | ||
| file_id: str, | ||
| ) -> None: | ||
| """Delete data with file_id. | ||
| Parameters | ||
| ---------- | ||
| table_name : str | ||
| name of table db | ||
| file_id : str | ||
| name of file in table | ||
| """ | ||
| table_name = self._meta[META.TABLE_PREFIX] + table_name | ||
| if not (self._storage / table_name / f"{file_id}.json").exists(): | ||
| raise FileNotFoundError | ||
| (self._storage / table_name / f"{file_id}.json").unlink() |
| # Copyright 2025 LangNeuron | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| """Errors.""" | ||
| from .eror_path_not_avaible import PathNotAvaibleError | ||
| from .error_data_is_uncorrect import DataIsUncorrectError | ||
| from .error_db_not_loaded import DbNotLoadedError | ||
| from .error_not_found import NotFoundColumnError, NotFoundTableError | ||
| from .error_unknown_data_type import UnknownDataTypeError | ||
| from .table_already_exist import TableAlreadyAvaibleError | ||
| __all__ = [ | ||
| "DataIsUncorrectError", | ||
| "DbNotLoadedError", | ||
| "NotFoundColumnError", | ||
| "NotFoundTableError", | ||
| "PathNotAvaibleError", | ||
| "TableAlreadyAvaibleError", | ||
| "UnknownDataTypeError", | ||
| ] |
| # Copyright 2025 LangNeuron | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| """Path / storage related errors.""" | ||
| class PathNotAvaibleError(FileNotFoundError): | ||
| """Raised when the configured database storage path is not available. | ||
| Note: class name preserves historical spelling for backward | ||
| compatibility with existing code and tests. | ||
| """ | ||
| def __str__(self) -> str: | ||
| """Return a readable message for this exception.""" | ||
| return "Database file not available" |
| # Copyright 2025 LangNeuron | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| """Eror DataIsUncorrectError.""" | ||
| from typing import Any | ||
| class DataIsUncorrectError(Exception): | ||
| """Error DataIsUncorrectError. | ||
| Parameters | ||
| ---------- | ||
| Exception : _type_ | ||
| Base exception | ||
| """ | ||
| def __init__(self, data: dict[str, Any]) -> None: | ||
| """Init. | ||
| Parameters | ||
| ---------- | ||
| table_name : str | ||
| name of table, when noot found | ||
| """ | ||
| self.data = data | ||
| super().__init__(f"Data is uncorrect, data: '{data}'.") | ||
| def __str__(self) -> str: | ||
| """Print Exception. | ||
| Returns | ||
| ------- | ||
| str | ||
| String info message | ||
| """ | ||
| return f"Error: Data is uncorrect {self.data}" |
| # Copyright 2025 LangNeuron | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| """Eror DbNotLoadedError.""" | ||
| class DbNotLoadedError(Exception): | ||
| """Error DbNotLoadedError. | ||
| Parameters | ||
| ---------- | ||
| Exception : _type_ | ||
| Base exception | ||
| """ | ||
| def __str__(self) -> str: | ||
| """Print Exception. | ||
| Returns | ||
| ------- | ||
| str | ||
| String info message | ||
| """ | ||
| return "Database not loaded" |
| # Copyright 2025 LangNeuron | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| """Eror DbNotLoadedError.""" | ||
| class NotFoundTableError(Exception): | ||
| """Error NotFoundError. | ||
| Parameters | ||
| ---------- | ||
| Exception : _type_ | ||
| Base exception | ||
| """ | ||
| def __init__(self, table_name: str) -> None: | ||
| """Init. | ||
| Parameters | ||
| ---------- | ||
| table_name : str | ||
| name of table, when noot found | ||
| """ | ||
| self.table_name = table_name | ||
| super().__init__(f"Table '{table_name}' not found.") | ||
| def __str__(self) -> str: | ||
| """Print Exception. | ||
| Returns | ||
| ------- | ||
| str | ||
| String info message | ||
| """ | ||
| return f"ERROR: TABLE **'{self.table_name}'** not found" | ||
| class NotFoundColumnError(Exception): | ||
| """Error NotFoundError. | ||
| Parameters | ||
| ---------- | ||
| Exception : _type_ | ||
| Base exception | ||
| """ | ||
| def __init__(self, column_name: str, table_name: str) -> None: | ||
| """Init. | ||
| Parameters | ||
| ---------- | ||
| table_name : str | ||
| name of table, when noot found | ||
| """ | ||
| self.column_name = column_name | ||
| self.table_name = table_name | ||
| super().__init__(f"Column '{column_name}' not found in {table_name}.") | ||
| def __str__(self) -> str: | ||
| """Print Exception. | ||
| Returns | ||
| ------- | ||
| str | ||
| String info message | ||
| """ | ||
| return f"Column '{self.column_name}' not found in {self.table_name}." |
| # Copyright 2025 LangNeuron | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| """Eror UnknownDataTypeError.""" | ||
| class UnknownDataTypeError(Exception): | ||
| """Error UnknownDataTypeError. | ||
| Parameters | ||
| ---------- | ||
| Exception : _type_ | ||
| Base exception | ||
| """ | ||
| def __str__(self) -> str: | ||
| """Print Exception. | ||
| Returns | ||
| ------- | ||
| str | ||
| String info message | ||
| """ | ||
| return "Unknown data type." |
| # Copyright 2025 LangNeuron | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| """Errors for table already exists condition.""" | ||
| class TableAlreadyAvaibleError(Exception): | ||
| """Raised when trying to create a table that already exists. | ||
| Note: class name preserves historical spelling for backward | ||
| compatibility with existing code and tests. | ||
| """ | ||
| def __str__(self) -> str: | ||
| """Return a readable message for this exception.""" | ||
| return "Table already available" |
| # Copyright 2025 LangNeuron | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| """FilesDB.""" | ||
| from __future__ import annotations | ||
| from pyfiles_db.database_manager import META, _DBasync, _DBsync | ||
| try: | ||
| from typing import Self | ||
| except ImportError: | ||
| from typing_extensions import Self # noqa: UP035 for Python < 3.11 | ||
| import json | ||
| from pathlib import Path | ||
| from typing import TYPE_CHECKING, Any, ClassVar | ||
| from .errors import ( | ||
| PathNotAvaibleError, | ||
| ) | ||
| if TYPE_CHECKING: | ||
| from pyfiles_db.database_manager._db import _AsyncDB | ||
| BASE_PATH_STORAGE = Path(__file__).parent.parent.parent / "database" | ||
| class FilesDB: | ||
| """FilesDB, manager for DB.""" | ||
| _instance: ClassVar[Self | None] = None | ||
| def __new__(cls: type[Self]) -> Self: | ||
| """ | ||
| Create or return the existing singleton instance. | ||
| Returns | ||
| ------- | ||
| FilesDB | ||
| The existing or newly created singleton instance. | ||
| """ | ||
| if not cls._instance: | ||
| cls._instance = super().__new__(cls) | ||
| return cls._instance | ||
| def init_sync(self, | ||
| storage: Path | str | None = None, | ||
| *, | ||
| meta_file: str = "meta.json", | ||
| meta: dict[str, Any] | None = None, | ||
| ) -> _DBsync: | ||
| """Initialize a new synchronous database connection. | ||
| If a database is already loaded this returns a connection. If not, | ||
| creates the base meta information and returns a new connection. | ||
| Parameters | ||
| ---------- | ||
| storage : Path | str | None, optional | ||
| Path to database location, by default None | ||
| meta_file : str, optional | ||
| Name of meta file, by default "meta.json" | ||
| Returns | ||
| ------- | ||
| _DBsync | ||
| Synchronous database manager instance. | ||
| """ | ||
| storage = self._configure_database( | ||
| storage=storage, | ||
| meta_file=meta_file, | ||
| meta=meta) | ||
| return _DBsync(storage=storage, meta_file=self._meta_file) | ||
| def init_async(self, | ||
| storage: Path | str | None = None, | ||
| *, | ||
| meta_file: str = "meta.json", | ||
| meta: dict[str, Any] | None = None, | ||
| ) -> _AsyncDB: | ||
| """Initialize a new asynchronous database connection. | ||
| If a database is already loaded this returns a connection. If not, | ||
| creates the base meta information and returns a new async manager. | ||
| Parameters | ||
| ---------- | ||
| storage : Path | str | None, optional | ||
| Path to database location, by default None | ||
| meta_file : str, optional | ||
| Name of meta file, by default "meta.json" | ||
| Returns | ||
| ------- | ||
| _AsyncDB | ||
| Asynchronous database manager instance. | ||
| """ | ||
| storage = self._configure_database( | ||
| storage=storage, | ||
| meta_file=meta_file, | ||
| meta=meta) | ||
| return _DBasync(storage=storage, meta_file=self._meta_file) | ||
| def _configure_database( | ||
| self, | ||
| storage: str | Path | None, | ||
| meta_file: str, | ||
| meta: dict[str, Any] | None, | ||
| )-> str | Path: | ||
| if meta is None: | ||
| meta = {} | ||
| self._meta_file = meta_file | ||
| if storage is None: | ||
| storage = BASE_PATH_STORAGE | ||
| if self._check_storage(storage=storage): | ||
| self._create_base_meta_information(storage, meta=meta) | ||
| return storage | ||
| def _base_meta(self) -> dict[str, Any]: | ||
| """Return base meta information. | ||
| Returns | ||
| ------- | ||
| dict[str, Any] | ||
| Base meta information. | ||
| """ | ||
| return { | ||
| META.TABLES: [], | ||
| META.ENCRYPTDB: False, | ||
| META.TABLE_PREFIX: "TABLE_", | ||
| } | ||
| def _valid_key_value(self, key: str, value: Any) -> None: # noqa: ANN401 | ||
| """Validate data type for meta. | ||
| Parameters | ||
| ---------- | ||
| key : str | ||
| key of meta information | ||
| value : Any | ||
| value of meta information | ||
| Raises | ||
| ------ | ||
| TypeError | ||
| When data type is not valid | ||
| """ | ||
| match(key): | ||
| case META.TABLES: | ||
| if not isinstance(value, list): | ||
| raise TypeError | ||
| case META.ENCRYPTDB: | ||
| if not isinstance(value, bool): | ||
| raise TypeError | ||
| def _configure_meta(self, meta: dict[str, Any]) -> dict[str, Any]: | ||
| """Configure meta information. | ||
| Parameters | ||
| ---------- | ||
| meta : dict[str, Any] | ||
| raw meta information from user | ||
| Returns | ||
| ------- | ||
| dict[str, Any] | ||
| meta information | ||
| """ | ||
| new_meta = self._base_meta() | ||
| for key, value in meta.items(): | ||
| self._valid_key_value(key, value) | ||
| new_meta[key] = value | ||
| return new_meta | ||
| def _create_base_meta_information(self, | ||
| storage: str | Path, | ||
| meta: dict[str, Any]) -> None: | ||
| """Create base meta structure files. | ||
| Parameters | ||
| ---------- | ||
| storage : str | Path | ||
| path to database location | ||
| meta : dict[str, Any] | ||
| raw meta information from user | ||
| """ | ||
| meta = self._configure_meta(meta) | ||
| storage = Path(storage) | ||
| with Path.open(storage / self._meta_file, "w") as f: | ||
| json.dump(meta, f) | ||
| def _check_storage(self, storage: str | Path) -> bool: | ||
| """Check storage availability and create folder if needed. | ||
| Parameters | ||
| ---------- | ||
| storage : str | Path | ||
| Path to database location. | ||
| Returns | ||
| ------- | ||
| bool | ||
| True if the meta file already exists (database exists). | ||
| False if the meta file does not exist yet. | ||
| Raises | ||
| ------ | ||
| PathNotAvaibleError | ||
| If the path is not available. | ||
| NotADirectoryError | ||
| If the path exists but is not a directory. | ||
| """ | ||
| storage = Path(storage) | ||
| storage.mkdir(parents=True, exist_ok=True) | ||
| if not storage.exists(): | ||
| raise PathNotAvaibleError | ||
| if not storage.is_dir(): | ||
| raise NotADirectoryError | ||
| return not (storage / self._meta_file).exists() | ||
| # Copyright 2025 LangNeuron | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| """Utils.""" | ||
| from .infinity_number_generator import infinite_natural_numbers | ||
| __all__ = ["infinite_natural_numbers"] |
| # Copyright 2025 LangNeuron | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| """Infinity number generator.""" | ||
| from collections.abc import Generator | ||
| from typing import Any | ||
| def infinite_natural_numbers(start: int) -> Generator[Any, Any, Any]: | ||
| """Generate numbers.""" | ||
| number = start | ||
| while True: | ||
| yield number | ||
| number += 1 |
-20
| pyfiles_db/__init__.py,sha256=wYXYhAPSfgj7OCuGoHfIRnE6HdZGgIcm8ZqneH-5k-w,649 | ||
| pyfiles_db/database_manager/__init__.py,sha256=iDygugsEbF_X90uq5_zFTnjzk9TrSZnj0d8AkA9T7pw,760 | ||
| pyfiles_db/database_manager/_db.py,sha256=ZBhu4ThtQ_HCahPjWG_Hv0Umvp419kUlZHQDuTL_Gd4,5605 | ||
| pyfiles_db/database_manager/async_db.py,sha256=nhy_gOYWNzZsPJSiJcUkBRHtP5_3uBvwa-pcJL1nGqs,10613 | ||
| pyfiles_db/database_manager/meta.py,sha256=JDD7KA256YRiFBDRIS2Ggzouu9PpbXmnywxLR8KmY5w,895 | ||
| pyfiles_db/database_manager/sync_db.py,sha256=YieiRCek0L7YUfu5wwE1FeHdfgnU_NKMGfPRPklJpx8,10202 | ||
| pyfiles_db/errors/__init__.py,sha256=ybotpf5UEu2_J0TRc3SjTO6GMtA1Jc0yptmPXj0wjoM,1195 | ||
| pyfiles_db/errors/eror_path_not_avaible.py,sha256=SZg-PN4WZDSjAyQR5mvRJzNWnobc3HqNaMrjxKlDC74,990 | ||
| pyfiles_db/errors/error_data_is_uncorrect.py,sha256=bBBfhDzkXmDDZ5JKw9YZOXtzsPp0bYKRRg9hx_Hejys,1276 | ||
| pyfiles_db/errors/error_db_not_loaded.py,sha256=6ExS8OHcxuAcbkpONkKyZIWZEhZNr5k5TQGeVKoWf4k,941 | ||
| pyfiles_db/errors/error_not_found.py,sha256=Y6caAMAC9NALI_3FZT0Y3TjmdxDpA4JVOQ9aMaUd26U,1989 | ||
| pyfiles_db/errors/error_unknown_data_type.py,sha256=qE2SyGVQJlXzwAawaBPmIau5PQwWaXxWF8aLm_EdDrE,952 | ||
| pyfiles_db/errors/table_already_exist.py,sha256=DP2CZxMRQg4VLqw0L6vmD05zs2FebPWl8tMnpbrxBR0,986 | ||
| pyfiles_db/files_db.py,sha256=JpdBUc023WrEQdlSsQ8sikmFeZ3yijo4Yhp774m_kfs,6909 | ||
| pyfiles_db/utils/__init__.py,sha256=LReCwEqrXeW9GO-aG6FTJ7eq1yH7Hk-kghseEV2Uq88,693 | ||
| pyfiles_db/utils/infinity_number_generator.py,sha256=a3wFLFGHsST4ADHfKamkOzlQzf1_7T3Rufqc8rpqXfs,846 | ||
| python_files_db-0.0.1a4.dist-info/METADATA,sha256=RocuAvpEv8tDHoousYYe0fVMbDbIeOZJNrcXJ-eEk-Y,3928 | ||
| python_files_db-0.0.1a4.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88 | ||
| python_files_db-0.0.1a4.dist-info/licenses/LICENSE,sha256=4EliqheUPkaMixxYQJsfT2oG_CnduWxs-m9VyoXGEog,11340 | ||
| python_files_db-0.0.1a4.dist-info/RECORD,, |
-4
| Wheel-Version: 1.0 | ||
| Generator: poetry-core 2.2.1 | ||
| Root-Is-Purelib: true | ||
| Tag: py3-none-any |
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
65526
Infinity%20
Infinity%1275
Infinity%