diff --git a/.gitignore b/.gitignore index ea74436..064ad48 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,11 @@ .idea/* aiodatalite/__pycache__/* *.pyc -datalite/*.db +aiodatalite/*.db *.db docs/_build/* build/* -datalite.egg-info/* +aiodatalite.egg-info/* dist/* venv/* t/ diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index d4bb2cb..0000000 --- a/docs/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line, and also -# from the environment for the first two. -SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build -SOURCEDIR = . -BUILDDIR = _build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/datalite.rst b/docs/aiodatalite.rst similarity index 55% rename from docs/datalite.rst rename to docs/aiodatalite.rst index 105aec2..33fc84e 100644 --- a/docs/datalite.rst +++ b/docs/aiodatalite.rst @@ -1,39 +1,39 @@ API Reference ================ -datalite Module +aiodatalite Module ----------------------------------- -.. autodecorator:: datalite.datalite +.. autodecorator:: aiodatalite.datalite -datalite.constraints module +aiodatalite.constraints module ---------------------------------- -.. automodule:: datalite.constraints +.. automodule:: aiodatalite.constraints :members: :undoc-members: :show-inheritance: -datalite.fetch module +aiodatalite.fetch module --------------------- -.. automodule:: datalite.fetch +.. automodule:: aiodatalite.fetch :members: :undoc-members: :show-inheritance: -datalite.mass_actions module +aiodatalite.mass_actions module ------------------------------ -.. automodule:: datalite.mass_actions +.. automodule:: aiodatalite.mass_actions :members: :undoc-members: :show-inheritance: -datalite.migrations module +aiodatalite.migrations module ---------------------------- -.. automodule:: datalite.migrations +.. automodule:: aiodatalite.migrations :members: :undoc-members: :show-inheritance: diff --git a/docs/conf.py b/docs/conf.py index f807b17..2d2c2d4 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -19,7 +19,7 @@ sys.path.insert(0, os.path.abspath("../.")) # -- Project information ----------------------------------------------------- -project = "Datalite" +project = "aiodatalite" copyright = "2020, Ege Ozkan; 2024, hhh" author = "Ege Ozkan, hhh" diff --git a/docs/constraints.rst b/docs/constraints.rst index 230b158..cb7a3a0 100644 --- a/docs/constraints.rst +++ b/docs/constraints.rst @@ -11,7 +11,7 @@ specific column **MUST** abide by specific constraints, these might be * Values of this column can be used to identify a record. (``PRIMARY``) * Values of this column has a default value. (``DEFAULT``) -Some of these constraints are already implemented in datalite. With all of the set, +Some of these constraints are already implemented in aiodatalite. With all of the set, is planned to be implemented in the future. Default Values @@ -53,7 +53,7 @@ Uniqueness constraint is declared thusly: .. code-block:: python - from datalite.constraints import Unique + from aiodatalite.constraints import Unique @datalite("db.db") @dataclass diff --git a/docs/decorator.rst b/docs/decorator.rst index ee3f3e2..d4a6994 100644 --- a/docs/decorator.rst +++ b/docs/decorator.rst @@ -4,14 +4,14 @@ Basic Decorator Operations Creating a datalite class ------------------------- -A datalite class is a special dataclass. It is created by using a decorator ``@datalite.datalite``, +A datalite class is a special dataclass. It is created by using a decorator ``@aiodatalite.datalite``, members of this class are, from Python's perspective, just normal classes. However, they have additional methods and attributes. ``@datalite`` decorator needs a database path to be provided. This database is the database the table for the dataclass will be created. .. code-block:: python - from datalite import datalite + from aiodatalite import datalite @datalite(db_path='db.db') @dataclass class Student: @@ -29,6 +29,18 @@ Special Methods Each object initialised from a dataclass decorated with the ``@dataclass`` decorator automatically gains access to three special methods. It should be noted, due to the nature of the library, extensions such as ``mypy`` and IDEs such as PyCharm will not be able to see these methods and may raise exceptions. +So, aiodatalite introduces ``typed`` module and ``DataliteHinted`` class, from which you can inherit your dataclass. + +.. code-block:: python + + from aiodatalite import datalite + from aiodatalite.typed import DataliteHinted + @datalite(db_path='db.db') + @dataclass + class Student(DataliteHinted): + student_id: int = 1 + student_name: str = "Kurt Gödel" + student_gpa: float = 3.9 With this in mind, let us create a new object and run the methods over this objects. @@ -36,6 +48,31 @@ With this in mind, let us create a new object and run the methods over this obje new_student = Student(0, "Albert Einstein", 4.0) +Marking up Table +################# +Due to the limitations of asynchronous programming, we cannot automatically create the table asynchronously, +so we provide two ways to do this. + +First way is to create the table automatically in !synchronous! mode by explicitly passing an argument to the decorator. +We don't know what consequences this can lead to specifically in your application, but if you are confident in yourself, +use this method + +.. code-block:: python + + ... + @datalite(db_path='db.db', automarkup=True) + @dataclass + class Student(DataliteHinted): + ... + +The second way, which may be less convenient when using some frameworks but is more controllable, is to call the +asynchronous ``create_table`` method, which will be added to your dataclass after using ``@datalite`` decorator, +alongside with some other methods. + +.. code-block:: python + + await new_student.markup_table() + Creating an Entry ################## @@ -46,7 +83,7 @@ in the table: .. code-block:: python - new_student.create_entry() + await new_student.create_entry() This also modifies the object in an intresting way, it adds a new attribute ``obj_id``, this is a unique, autoincremented value in the database. It can be accessed by ``new_student.obj_id``. @@ -60,7 +97,7 @@ record in the database, this method must be called. .. code-block:: python new_student.student_gpa = 5.0 # He is Einstein, after all. - new_student.update_entry() + await new_student.update_entry() Deleting an Entry @@ -71,9 +108,57 @@ be used. .. code-block:: python - new_student.remove_entry() + await new_student.remove_entry() .. warning:: It should be noted that, if the ``new_student.obj_id`` attribute is modified, ``.update_entry()`` and ``.remove_entry()`` may have unexpected results. + + +Tweaked Types +-------------- +Sometimes your objects may be somewhat complex or use a nested structure. This fork allows nesting by using the pickle +module, which gives you the ability to turn your objects into pure bytes that can be written to and from the database. +When the ``tweaked`` parameter is enabled, data that can be written natively is written as is, and data that cannot be +written in this way is first processed by pickle. + +.. code-block:: python + + from aiodatalite import datalite + from aiodatalite.typed import DataliteHinted + + # Bag dataclass defined somewhere + + @datalite(db_path='db.db') + @dataclass + class Student(DataliteHinted): + student_id: int = 1 + student_name: str = "Kurt Gödel" + student_gpa: float = 3.9 + bag: Bag = Bag( + size="big", + items_number=88, + ... + ) + +But with great opportunity comes great responsibility, so using nested models can lead to difficulties in migrating data +and updating the structure of nested objects, so changing the ``Bag`` object in this example, in theory, can break the +database without the possibility of migration. We recommend using multiple tables and simple relationships between them +(must be organized independently) in such cases. + +The tweaked functionality of types can be disabled by passing the tweaked parameter as False to the datalite decorator +.. code-block:: python + + from aiodatalite import datalite + from aiodatalite.typed import DataliteHinted + + # Bag is defined somewhere as datalite table instance + + @datalite(db_path='db.db', tweaked=False) + @dataclass + class Student(DataliteHinted): + bag_id: int + student_id: int = 1 + student_name: str = "Kurt Gödel" + student_gpa: float = 3.9 diff --git a/docs/fetch.rst b/docs/fetch.rst index 1dc40ec..f48ba1c 100644 --- a/docs/fetch.rst +++ b/docs/fetch.rst @@ -1,11 +1,12 @@ Fetching Functions =================== -A database is hardly useful if data does not persist between program runs. In ``datalite`` -one can use ``datalite.fetch`` module to fetch data back from the database. +A database is hardly useful if data does not persist between program runs. In ``aiodatalite`` +one can use ``aiodatalite.fetch`` module to fetch data back from the database. There are different sorts of fetching. One can fetch all the objects of a class -using ``fetch_all(class_)``, or an object with a specific object id using ``fetch_from(class_, obj_id)``. +using ``await fetch_all(class_)``, or an object with a specific object id using ``await fetch_from(class_, obj_id)`` +(useful when you know object id inside database, but lost the reference to the object itself somewhere). There are more functions for plural conditional fetching (``fetch_if``, ``fetch_where``) where all objects fitting a condition will be returned, as well as singular conditional fetching that returns the first object that fits a condition (``fetch_equals``). @@ -13,7 +14,7 @@ the first object that fits a condition (``fetch_equals``). Pagination ########## -Pagination is a feature that allows a portion of the results to be returned. Since ``datalite`` +Pagination is a feature that allows a portion of the results to be returned. Since ``aiodatalite`` is built to work with databases that may include large amounts of data, many systems using large portions of data also make use of pagination. By building pagination inside the system, we hope to allow easier usage. @@ -29,4 +30,4 @@ each page has. When ``page`` is set to 0, all results are returned irregardless .. important:: - More information regarding the ``datalite.fetch`` functions can be found in the API reference. \ No newline at end of file + More information regarding the ``aiodatalite.fetch`` functions can be found in the API reference. diff --git a/docs/index.rst b/docs/index.rst index 5b108a9..2f1b39d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,13 +1,7 @@ -.. Datalite documentation master file, created by - sphinx-quickstart on Thu Aug 13 21:01:34 2020. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - - -Welcome to Datalite's documentation! +Welcome to aioatalite's documentation! ==================================== -Datalite is a simple to use Python library that can bind a dataclass to an +aioatalite is a simple to use async Python library that can bind a dataclass to an sqlite3 database. @@ -22,7 +16,7 @@ Documentation constraints fetch migration - datalite + aiodatalite diff --git a/docs/installation.rst b/docs/installation.rst index 18d6cf5..dce6a8f 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -1,10 +1,12 @@ Getting Started ================= -Welcome to the documentation of datalite. Datalite provides a simple, intuitive way to bind dataclasses -to sqlite3 databases. In its current version, it provides implicit support for conversion between -``int``, ``float``, ``str``, ``bytes`` classes and their ``sqlite3`` counterparts, default values, +Welcome to the documentation of aiodatalite, an asynchronous fork of datalite. Datalite provides a simple, intuitive +way to bind dataclasses to sqlite3 databases. In its current version, it provides implicit support for conversion +between ``int``, ``float``, ``str``, ``bytes`` classes and their ``sqlite3`` counterparts, default values, basic schema migration and fetching functions. +Also, aiodatalite introduces ``tweaked`` parameter (True by default), which allows using pickle to store any values +in database Installation ############ @@ -13,11 +15,11 @@ Simply write: .. code-block:: bash - pip install datalite + pip install aiodatalite In the shell. And then, whenever you want to use it in Python, you can use: .. code-block:: python - import datalite + import aiodatalite diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index 2119f51..0000000 --- a/docs/make.bat +++ /dev/null @@ -1,35 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=. -set BUILDDIR=_build - -if "%1" == "" goto help - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% - -:end -popd diff --git a/docs/migration.rst b/docs/migration.rst index 3310705..d15418c 100644 --- a/docs/migration.rst +++ b/docs/migration.rst @@ -1,8 +1,8 @@ Schema Migrations ================== -Datalite provides a module, ``datalite.migrations`` that handles schema migrations. When a class -definition is modified, ``datalite.migrations.basic_migration`` can be called to automatically +Datalite provides a module, ``aiodatalite.migrations`` that handles schema migrations. When a class +definition is modified, ``aiodatalite.migrations.migrate`` can be called to transfer records to a table fitting the new definitions. Let us say we have made changes to the fields of a dataclass called ``Student`` and now, @@ -13,7 +13,34 @@ name change. We can achieve this easily by: .. code-block:: python - datalite.basic_migration(Student, {'studentt_id': 'student_id'}) + await migrate(Student, {'studentt_id': 'student_id'}) This will make all the changes, if we had not provided the second argument, -the values would be lost. \ No newline at end of file +the values would be lost. + +Also, ``migrate`` provides automatic backup before migration, you can turn it off by passing ``do_backup=False`` into +function. + +We also introduce safe migration defaults. This parameter should be passed a key-value dictionary, where the key is the +name of the new required field, and the value is what should be written to the old records in the database. + +.. code-block:: python + + from aiodatalite import datalite + from aiodatalite.typed import DataliteHinted + @datalite(db_path='db.db') + @dataclass + class Student(DataliteHinted): + new_obligatory_field: str # the database will break if not all records have this field + student_id: int = 1 + student_name: str = "Kurt Gödel" + student_gpa: float = 3.9 + +So in this situation basically what you need to do is: + +.. code-block:: python + + await migrate(Student, safe_migration_defaults={ + "new_obligatory_field": "some value for records that already exist in the database" + } + )