From d053885caec41be2d920584805d40922a97a0569 Mon Sep 17 00:00:00 2001 From: Grant Ramsay Date: Thu, 31 Oct 2024 17:42:54 +0000 Subject: [PATCH] Deployed 092ab9c with MkDocs version: 1.6.1 --- search/search_index.json | 2 +- todo/index.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/search/search_index.json b/search/search_index.json index 6dc1591..42ee0c6 100644 --- a/search/search_index.json +++ b/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"SQLiter","text":"

SQLiter is a lightweight Object-Relational Mapping (ORM) library for SQLite databases in Python. It provides a simplified interface for interacting with SQLite databases using Pydantic models. The only external run-time dependency is Pydantic itself.

It does not aim to be a full-fledged ORM like SQLAlchemy, but rather a simple and easy-to-use library for basic database operations, especially for small projects. It is NOT asynchronous and does not support complex queries (at this time).

The ideal use case is more for Python CLI tools that need to store data in a database-like format without needing to learn SQL or use a full ORM.

Caution

This project is still in the early stages of development and is lacking some planned functionality. Please use with caution - Classes and methods may change until a stable release is made. I'll try to keep this to an absolute minimum and the releases and documentation will be very clear about any breaking changes.

Also, structures like list, dict, set etc are not supported at this time as field types, since SQLite does not have a native column type for these. This is the next planned enhancement. These will need to be pickled first then stored as a BLOB in the database.

See the TODO for planned features and improvements.

"},{"location":"#features","title":"Features","text":""},{"location":"#license","title":"License","text":"

This project is licensed under the terms of the MIT license.

"},{"location":"installation/","title":"Installation","text":"

You can install SQLiter using whichever method you prefer or is compatible with your project setup.

With uv which is rapidly becoming my favorite tool for managing projects and virtual environments (uv is used for developing this project and in the CI):

uv add sqliter-py\n

With Poetry:

poetry add sqliter-py\n

Or with pip:

pip install sqliter-py\n
"},{"location":"installation/#optional-dependencies","title":"Optional Dependencies","text":"

Currently by default, the only external dependency is Pydantic. However, there are some optional dependencies that can be installed to enable additional features:

These can be installed using uv:

uv add 'sqliter-py[extras]'\n

With Poetry:

poetry add 'sqliter-py[extras]'\n

Or with pip:

pip install 'sqliter-py[extras]'\n
"},{"location":"license/","title":"License","text":"

This project is licensed under the terms of the MIT license below:

The MIT License (MIT)\nCopyright (c) 2024 Grant Ramsay\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\nDAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\nOTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE\nOR OTHER DEALINGS IN THE SOFTWARE.\n
"},{"location":"quickstart/","title":"Quick Start","text":"

Here's a quick example of how to use SQLiter:

from typing import Optional\n\nfrom sqliter import SqliterDB\nfrom sqliter.model import BaseDBModel\n\n# Define your model\nclass User(BaseDBModel):\n    name: str\n    age: int\n    admin: Optional[bool] = False\n\n# Create a database connection\ndb = SqliterDB(\"example.db\")\n\n# Create the table\ndb.create_table(User)\n\n# Insert a record\nuser = User(name=\"John Doe\", age=30)\nnew_user = db.insert(user)\n\n# Query records\nresults = db.select(User).filter(name=\"John Doe\").fetch_all()\nfor user in results:\n    print(f\"User: {user.name}, Age: {user.age}, Admin: {user.admin}\")\n\n# Update a record\nnew_user.age = 31\ndb.update(new_user)\n\nresults = db.select(User).filter(name=\"John Doe\").fetch_one()\n\nprint(\"Updated age:\", results.age)\n\n# Delete a record\ndb.delete(User, new_user.pk)\n

See the Guide for more detailed information on how to use SQLiter.

"},{"location":"changelog/","title":"Changelog","text":"

This is an auto-generated log of all the changes that have been made to the project since the first release, with the latest changes at the top.

This project adheres to Semantic Versioning.

"},{"location":"changelog/#070-october-31-2024","title":"0.7.0 (October 31, 2024)","text":"

New Features

Bug Fixes

Full Changelog | Diff | Patch

"},{"location":"changelog/#060-october-12-2024","title":"0.6.0 (October 12, 2024)","text":"

New Features

Bug Fixes

Full Changelog | Diff | Patch

"},{"location":"changelog/#050-september-30-2024","title":"0.5.0 (September 30, 2024)","text":"

Breaking Change!

This release removes the create_pk and primary_key attributes from the Model Meta Class. Now, an auto-incrementing primary key is created by default and the name of the primary key is always pk.

Closed Issues

Breaking Changes

Bug Fixes

Documentation

Full Changelog | Diff | Patch

"},{"location":"changelog/#040-september-27-2024","title":"0.4.0 (September 27, 2024)","text":"

New Features

Testing

Refactoring

Documentation

Full Changelog | Diff | Patch

"},{"location":"changelog/#030-september-23-2024","title":"0.3.0 (September 23, 2024)","text":"

Breaking Changes

New Features

Dependency Updates

Full Changelog | Diff | Patch

"},{"location":"changelog/#020-september-14-2024","title":"0.2.0 (September 14, 2024)","text":"

New Features

Bug Fixes

Full Changelog | Diff | Patch

"},{"location":"changelog/#011-september-12-2024","title":"0.1.1 (September 12, 2024)","text":"

Just a documentation fix - README was old version

Full Changelog | Diff | Patch

"},{"location":"changelog/#010-september-12-2024","title":"0.1.0 (September 12, 2024)","text":"

New Features

Testing

Dependency Updates

This changelog was generated using github-changelog-md by Seapagan

"},{"location":"contributing/","title":"Contributing to SQLiter","text":"

Thank you for your interest in contributing to SQLiter! We welcome all contributions, big or small.

If you are not sure where to start, please take a look at the open issues. If you have an idea for a new feature or would like to report a bug, please open a new issue.

We also welcome contributions to the documentation. If you find any errors or would like to suggest improvements, please open a new issue or submit a pull

"},{"location":"contributing/#prerequisites","title":"Prerequisites","text":"

uv can be used to actually install Python, even if you do not have it installed locally (either by system, pyenv or similar).

For example, to install Python 3.12 using uv, you can run the following command:

uv python install 3.12\n

If you already have a Python version installed, uv will use this.

"},{"location":"contributing/#getting-started","title":"Getting Started","text":"

Before you start contributing, please make sure you have read and understood our Code of Conduct and License.

To get started, follow these steps:

  1. Fork the repository and clone it to your local machine.
  2. Install the required dependencies (see next section).
  3. Create a new branch for your changes: git checkout -b my-new-feature.
  4. Make your changes and commit them: git commit -am 'Add some feature'.
  5. Push your changes to your fork: git push origin my-new-feature.
  6. Create a new pull request.
"},{"location":"contributing/#install-dependencies","title":"Install Dependencies","text":"

Run the following command to create a local virtualenv and install the required dependencies. We need the optional extras installed so the tests pass:

uv sync --all-extras\n

The .venv folder is already in the .gitignore file so will not be committed to the repository. This is where the virtual environment will be created.

You then need to activate the virtual environment:

source .venv/bin/activate\n

From here you can start working on the project. If you are using an IDE such as VSCode or PyCharm, you can set their Python interpreter setting to use the virtual environment that has just been created.

"},{"location":"contributing/#install-git-pre-commit-hooks","title":"Install Git Pre-Commit hooks","text":"

Please do this if you are intending to submit a PR. It will check commits locally before they are pushed up to the Repo.

$ pre-commit install\npre-commit installed at .git/hooks/pre-commit\n

This will ensure that all code meets the required linting standard before being committed.

"},{"location":"contributing/#run-pre-commit-manually","title":"Run pre-commit manually","text":"

You can run these checks manually on all staged files using the below command :

poe pre\n
"},{"location":"contributing/#testing","title":"Testing","text":"

We are using pytest for testing. Tests will automatically be run when you submit a pull request. You can also run them manually using the following command:

pytest\n

If you add any new features, please add tests for them. This will help us to ensure that the code is working as expected and will prevent any regressions.

"},{"location":"contributing/#changelog","title":"Changelog","text":"

The changelog is automatically generated using github-changelog-md, so please do not edit it manually.

For maintainers, there is a POE task that will run this and update the changelog file.

poe changelog\n

You would also need to add a GitHub Personal Access Token to a local config file as usual. See the section in that tools Documentation for information.

However, you should NOT include a change to the CHANGELOG.md file in any Pull Requests. This will be handled by the maintainers when a new release is made. Your GitHub username will be added to the changelog automatically beside your PR.

"},{"location":"contributing/#convenience-tasks","title":"Convenience Tasks","text":"

There are a few other convenience tasks that can be run using the poe command. These are defined in the pyproject.toml file.

Each of these tasks can have extra options added which will be passed to the underlying tool.

Run mypy on the code base in strict mode:

poe mypy\n

Format the code using ruff format:

poe format\n

Lint the code using ruff check:

poe ruff\n
"},{"location":"contributing/#documentation-tasks","title":"Documentation Tasks","text":"

These are to help with developing and updating the documentation.

"},{"location":"contributing/#guidelines","title":"Guidelines","text":"

Here are some guidelines to follow when contributing to SQLiter:

"},{"location":"contributing/#contact","title":"Contact","text":"

If you have any questions or need help with contributing, please contact me @seapagan on GitHub. You can also use the GitHub Discussions feature.

Happy contributing!

"},{"location":"guide/connecting/","title":"Connecting to the Database","text":""},{"location":"guide/connecting/#creating-a-connection","title":"Creating a Connection","text":"

To connect to a database (and create the file if it does not already exist), you create an instance of the SqliterDB class. This will automatically take care of connecting to or creating the database file.

from sqliter import SqliterDB\n\ndb = SqliterDB(\"your_database.db\")\n

The default behavior is to automatically commit changes to the database after each operation. If you want to disable this behavior, you can set auto_commit=False when creating the database connection:

db = SqliterDB(\"your_database.db\", auto_commit=False)\n

It is then up to you to manually commit changes using the commit() method. This can be useful when you want to perform multiple operations in a single transaction without the overhead of committing after each operation.

"},{"location":"guide/connecting/#using-an-in-memory-database","title":"Using an In-Memory Database","text":"

If you want to use an in-memory database, you can set memory=True when creating the database connection:

db = SqliterDB(memory=True)\n

This will create an in-memory database that is not persisted to disk. If you also specify a database name, it will be ignored.

db = SqliterDB(\"ignored.db\", memory=True)\n

The ignored.db file will not be created, and the database will be in-memory. If you do not specify a database name, and do NOT set memory=True, an exception will be raised.

Note

You can also use \":memory:\" as the database name (same as normal with Sqlite) to create an in-memory database, this is just a cleaner and more descriptive way to do it.

db = SqliterDB(\":memory:\")\n
"},{"location":"guide/connecting/#resetting-the-database","title":"Resetting the Database","text":"

If you want to reset an existing database when you create the SqliterDB object, you can pass reset=True:

db = SqliterDB(\"your_database.db\", reset=True)\n

This will effectively drop all user tables from the database. The file itself is not deleted, only the tables are dropped.

"},{"location":"guide/connecting/#database-properties","title":"Database Properties","text":"

The SqliterDB class provides several properties to access information about the database instance once it has been created. See the Properties page (next) for more details.

"},{"location":"guide/data-operations/","title":"Data Operations","text":""},{"location":"guide/data-operations/#inserting-records","title":"Inserting Records","text":"

The insert() method is used to add records to the database. You pass an instance of your model class to the method, and SQLiter will insert the record into the correct table:

user = User(name=\"Jane Doe\", age=25, email=\"jane@example.com\")\nresult = db.insert(user)\n

The result variable will contain a new instance of the model, with the primary key value set to the newly-created primary key in the database. You should use this instance to access the primary key value and other fields:

print(f\"New record inserted with primary key: {result.pk}\")\nprint(f\"Name: {result.name}, Age: {result.age}, Email: {result.email}\")\n
"},{"location":"guide/data-operations/#overriding-the-timestamps","title":"Overriding the Timestamps","text":"

By default, SQLiter will automatically set the created_at and updated_at fields to the current Unix timestamp in UTC when a record is inserted. If you want to override this behavior, you can set the created_at and updated_at fields manually before calling insert():

import time\n\nuser.created_at = int(time.time())\nuser.updated_at = int(time.time())\n

However, by default this is disabled. Any model passed to insert() will have the created_at and updated_at fields set automatically and ignore any values passed in these 2 fields.

If you want to enable this feature, you can set the timestamp_override flag to True when inserting the record:

result = db.insert(user, timestamp_override=True)\n

Important

The insert() method will raise a RecordInsertionError if you try to insert a record with a primary key that already exists in the table or if the table does not exist.

"},{"location":"guide/data-operations/#querying-records","title":"Querying Records","text":"

SQLiter provides a simple and intuitive API for querying records from the database, Starting with the select() method and chaining other methods to filter, order, limit, and offset the results:

# Fetch all users\nall_users = db.select(User).fetch_all()\n\n# Filter users\nyoung_users = db.select(User).filter(age=25).fetch_all()\n\n# Order users\nordered_users = db.select(User).order(\"age\", reverse=True).fetch_all()\n\n# Limit and offset\npaginated_users = db.select(User).limit(10).offset(20).fetch_all()\n

Important

The select() MUST come first, before any filtering, ordering, or pagination etc. This is the starting point for building your query.

See Filtering Results for more advanced filtering options.

"},{"location":"guide/data-operations/#updating-records","title":"Updating Records","text":"

You can update records in the database by modifying the fields of the model instance and then calling the update() method. You just pass the model instance to the method:

user.age = 26\ndb.update(user)\n

Important

The model you pass must have a primary key value set, otherwise an error will be raised. In other words, you use the instance of a model returned by the insert() method to update the record as it has the primary key value set, not the original instance you passed to insert().

You can also set the primary key value on the model instance manually before calling update() if you have that.

On suffescul update, the updated_at field will be set to the current Unix timestamp in UTC by default.

Warning

Unlike with the insert() method, you CANNOT override the updated_at field when calling update(). It will always be set to the current Unix timestamp in UTC. This is to ensure that the updated_at field is always accurate.

"},{"location":"guide/data-operations/#deleting-records","title":"Deleting Records","text":"

To delete a record from the database, you need to pass the model class and the primary key value of the record you want to delete:

db.delete(User, user.pk)\n
"},{"location":"guide/data-operations/#commit-your-changes","title":"Commit your changes","text":"

By default, SQLiter will automatically commit changes to the database after each operation. If you want to disable this behavior, you can set auto_commit=False when creating the database connection:

db = SqliterDB(\"your_database.db\", auto_commit=False)\n

You can then manually commit changes using the commit() method:

db.commit()\n

Note

If you are using the database connection as a context manager (see tansactions), you do not need to call commit() explicitly. The connection will be closed automatically when the context manager exits, and any changes will be committed.

"},{"location":"guide/data-operations/#close-the-connection","title":"Close the Connection","text":"

When you're done with the database connection, you should close it to release resources:

db.close()\n

Note that closing the connection will also commit any pending changes, unless auto_commit is set to False.

Note

If you are using the database connection as a context manager (see tansactions), you do not need to call close() explicitly. The connection will be closed automatically when the context manager exits, and any changes will be committed.

"},{"location":"guide/debug/","title":"Debug Logging","text":"

You can enable debug logging to see the SQL queries being executed by SQLiter. This can be useful for debugging and understanding the behavior of your application. It is disabled by default, and can be set on the SqliterDB class:

db = SqliterDB(\"your_database.db\", debug=True)\n

This will print the SQL queries to the console as they are executed. If there is an existing logger in your application then SQLiter will use that logger, otherwise it will create and use a new logger named sqliter.

"},{"location":"guide/exceptions/","title":"Exceptions","text":"

SQLiter includes several custom exceptions to handle specific errors that may occur during database operations. These exceptions inherit from a common base class, SqliterError, to ensure consistency across error messages and behavior.

"},{"location":"guide/fields/","title":"Field Control","text":""},{"location":"guide/fields/#selecting-specific-fields","title":"Selecting Specific Fields","text":"

By default, all commands query and return all fields in the table. If you want to select only specific fields, you can pass them using the fields() method:

results = db.select(User).fields([\"name\", \"age\"]).fetch_all()\n

This will return only the name and age fields for each record.

You can also pass this as a parameter to the select() method:

results = db.select(User, fields=[\"name\", \"age\"]).fetch_all()\n

Note that using the fields() method will override any fields specified in the 'select()' method.

"},{"location":"guide/fields/#excluding-specific-fields","title":"Excluding Specific Fields","text":"

If you want to exclude specific fields from the results, you can use the exclude() method:

results = db.select(User).exclude([\"email\"]).fetch_all()\n

This will return all fields except the email field.

You can also pass this as a parameter to the select() method:

results = db.select(User, exclude=[\"email\"]).fetch_all()\n
"},{"location":"guide/fields/#returning-exactly-one-explicit-field-only","title":"Returning exactly one explicit field only","text":"

If you only want to return a single field from the results, you can use the only() method:

result = db.select(User).only(\"name\").fetch_first()\n

This will return only the name field for the first record.

This is exactly the same as using the fields() method with a single field, but very specific and obvious. There is NO equivalent argument to this in the select() method. An exception WILL be raised if you try to use this method with more than one field.

"},{"location":"guide/filtering/","title":"Filtering Results","text":"

The filter() method in SQLiter supports various filter options to query records, and can be combined with other methods like order(), limit(), and offset() to build more complex queries:

result = db.select(User).filter(age__lte=30).limit(10).fetch_all()\n

It is possible to both add multiple filters in the same call, and to chain multiple filter calls together:

result = db.select(User).filter(age__gte=20, age__lte=30).fetch_all()\n
result = db.select(User).filter(age__gte=20).filter(age__lte=30).fetch_all()\n
"},{"location":"guide/filtering/#basic-filters","title":"Basic Filters","text":""},{"location":"guide/filtering/#null-checks","title":"Null Checks","text":""},{"location":"guide/filtering/#comparison-operators","title":"Comparison Operators","text":""},{"location":"guide/filtering/#list-operations","title":"List Operations","text":""},{"location":"guide/filtering/#string-operations-case-sensitive","title":"String Operations (Case-Sensitive)","text":""},{"location":"guide/filtering/#string-operations-case-insensitive","title":"String Operations (Case-Insensitive)","text":""},{"location":"guide/guide/","title":"SQLiter Overview","text":"

SQLiter is a lightweight Python library designed to simplify database operations using Pydantic models. It provides a range of functionality including table creation, CRUD operations, querying, filtering, and more. This overview briefly introduces each feature.

"},{"location":"guide/guide/#basic-setup","title":"Basic Setup","text":"

To get started, import the necessary modules and define a Pydantic model for your table:

from sqliter import SqliterDB\nfrom sqliter.model import BaseDBModel\n\nclass User(BaseDBModel):\n    name: str\n    age: int\n    email: str\n\n# Create a database connection\ndb = SqliterDB(\"example.db\")\n
"},{"location":"guide/guide/#table-creation","title":"Table Creation","text":"

SQLiter allows you to create tables automatically based on your models:

db.create_table(User)\n

This creates a table for the User model, with fields based on the attributes of the model.

"},{"location":"guide/guide/#inserting-records","title":"Inserting Records","text":"

Inserting records is straightforward with SQLiter:

user = User(name=\"John Doe\", age=30, email=\"john@example.com\")\nnew_record = db.insert(user)\n

If successful, new_record will contain a model the same as was passed to it, but including the newly-created primary key value.

"},{"location":"guide/guide/#basic-queries","title":"Basic Queries","text":"

You can easily query all records from a table:

all_users = db.select(User).fetch_all()\n
"},{"location":"guide/guide/#filtering-results","title":"Filtering Results","text":"

SQLiter allows filtering of results using various conditions:

young_users = db.select(User).filter(age__lt=30).fetch_all()\n
"},{"location":"guide/guide/#fetching-records","title":"Fetching Records","text":"

SQLiter provides methods to fetch multiple, single, or the last record in a table.

"},{"location":"guide/guide/#fetching-all-records","title":"Fetching All Records","text":"

The fetch_all() method retrieves all records from the table that match the query or filter:

all_users = db.select(User).fetch_all()\n

This returns a list of all matching records. If no record matches, an empty list is returned.

"},{"location":"guide/guide/#fetching-one-record","title":"Fetching One Record","text":"

The fetch_one() method retrieves a single record that matches the query or filter:

result = db.select(User).filter(name=\"John Doe\").fetch_one()\n

If no record is found, None is returned.

"},{"location":"guide/guide/#fetching-the-last-record","title":"Fetching the Last Record","text":"

The fetch_last() method retrieves the last record in the table, typically based on the rowid:

last_user = db.select(User).fetch_last()\n

This fetches the most recently inserted record. If no record is found, None is returned.

"},{"location":"guide/guide/#updating-records","title":"Updating Records","text":"

Records can be updated seamlessly. Simply modify the fields of the model instance and pass that to the update() method:

user.age = 31\ndb.update(user)\n
"},{"location":"guide/guide/#deleting-records","title":"Deleting Records","text":"

Deleting records is simple as well. You just need to pass the Model that defines your table and the primary key value of the record you want to delete:

db.delete(User, 1)\n

Note

You can get the primary key value from the record or model instance itself, e.g., new_record.pk and pass that as the second argument to the delete() method:

db.delete(User, new_record.pk)\n
"},{"location":"guide/guide/#advanced-query-features","title":"Advanced Query Features","text":""},{"location":"guide/guide/#ordering","title":"Ordering","text":"

SQLiter supports ordering of results by specific fields:

ordered_users = db.select(User).order(\"age\", reverse=True).fetch_all()\n
"},{"location":"guide/guide/#limiting-and-offsetting","title":"Limiting and Offsetting","text":"

Pagination is supported through limit() and offset():

paginated_users = db.select(User).limit(10).offset(20).fetch_all()\n
"},{"location":"guide/guide/#transactions","title":"Transactions","text":"

SQLiter supports transactions using Python's context manager. This ensures that a group of operations are executed atomically, meaning either all of the operations succeed or none of them are applied.

To use transactions, simply wrap the operations within a with block:

with db:\n    db.insert(User(name=\"Alice\", age=30, email=\"alice@example.com\"))\n    db.insert(User(name=\"Bob\", age=35, email=\"bob@example.com\"))\n    # If an exception occurs here, both inserts will be rolled back\n

If an error occurs within the transaction block, all changes made inside the block will be rolled back automatically.

If no errors occur, the transaction will commit and changes will be saved. The close() method will also be called when the context manager exits, so there is no need to call it manually.

"},{"location":"guide/guide/#closing-the-database","title":"Closing the Database","text":"

Always remember to close the connection when you're done:

db.close()\n

Note

If you are using the database connection as a context manager (see above), you do not need to call close() explicitly. The connection will be closed automatically when the context manager exits, and any changes will be committed.

This is a quick look at the core features of SQLiter. For more details on each functionality, see the next section.

"},{"location":"guide/models/","title":"Models","text":"

Each individual table in your database should be represented by a model. This model should inherit from BaseDBModel and define the fields that should be stored in the table. Under the hood, the model is a Pydantic model, so you can use all the features of Pydantic models, such as default values, type hints, and validation.

"},{"location":"guide/models/#defining-models","title":"Defining Models","text":"

Models are defined like this:

from sqliter.model import BaseDBModel\n\nclass User(BaseDBModel):\n    name: str\n    age: int\n    email: str\n

You can create as many Models as you need, each representing a different table in your database. The fields in the model will be used to create the columns in the table.

Important

"},{"location":"guide/models/#field-types","title":"Field Types","text":"

The following field types are currently supported:

More field types are planned for the near future, since I have the serialization/ deserialization locked in. This will include list, dict, set, and possibly JSON and Object fields.

"},{"location":"guide/models/#adding-indexes","title":"Adding Indexes","text":"

You can add indexes to your table by specifying the indexes attribute in the Meta class. This should be a list of strings, each string being the name of an existing field in the model that should be indexed.

from sqliter.model import BaseDBModel\n\nclass User(BaseDBModel):\n    name: str\n    age: int\n    email: str\n\n    class Meta:\n        indexes = [\"name\", \"email\"]\n

This is in addition to the primary key index (pk) that is automatically created.

"},{"location":"guide/models/#adding-unique-indexes","title":"Adding Unique Indexes","text":"

You can add unique indexes to your table by specifying the unique_indexes attribute in the Meta class. This should be a list of strings, each string being the name of an existing field in the model that should be indexed.

from sqliter.model import BaseDBModel\n\nclass User(BaseDBModel):\n    name: str\n    age: int\n    email: str\n\n    class Meta:\n        unique_indexes = [\"email\"]\n

These will ensure that all values in this field are unique. This is in addition to the primary key index (pk) that is automatically created.

Tip

You can specify both indexes and unique_indexes in the Meta class if you need to.

"},{"location":"guide/models/#unique-fields","title":"Unique Fields","text":"

You can also specify that a field should be all unique values by using the Unique() method from the sqliter.model module. This will ensure that all values in this field are unique.

from typing import Annotated\nfrom sqliter.model import BaseDBModel, Unique\n\nclass User(BaseDBModel):\n    name: str\n    age: int\n    email: Annotated[str, Unique()]\n

This will raise either a RecordInsertionError or RecordUpdateError if you try to insert or update a record with a duplicate value in the chosen field.

Tip

Using Annotated is optional, but without it your code wil not pass type-checking with mypy. It will work fine at runtime but is not recommended:

email: str = Unique()\n

This will give the following Mypy error:

error: Incompatible types in assignment (expression has type \"Unique\", variable has type \"str\")  [assignment]\n
"},{"location":"guide/models/#custom-table-name","title":"Custom Table Name","text":"

By default, the table name will be the same as the model name, converted to 'snake_case' and pluralized (e.g., User -> users). Also, any 'Model' suffix will be removed (e.g., UserModel -> users). To override this behavior, you can specify the table_name in the Meta class manually as below:

from sqliter.model import BaseDBModel\n\nclass User(BaseDBModel):\n    name: str\n    age: int\n    email: str\n\n    class Meta:\n        table_name = \"people\"\n

Note

The pluralization is pretty basic by default, and just consists of adding an 's' if not already there. This will fail on words like 'person' or 'child'. If you need more advanced pluralization, you can install the extras package as mentioned in the installation. Of course, you can always specify the table_name manually in this case!

"},{"location":"guide/models/#model-classmethods","title":"Model Classmethods","text":"

There are 2 useful methods you can call on your models. Note that they are Class Methods so should be called on the Model class itself, not an instance of the model:

"},{"location":"guide/models/#get_table_name","title":"get_table_name()","text":"

This method returns the actual table name for the model either specified or automatically generated. This is useful if you need to do any raw SQL queries.

table_name = User.get_table_name()\n
"},{"location":"guide/models/#get_primary_key","title":"get_primary_key()","text":"

This simply returns the name of the primary key for that table. At the moment, this will always return the string pk but this may change in the future.

primary_key = User.get_primary_key()\n
"},{"location":"guide/ordering/","title":"Ordering","text":"

For now we only support ordering by the single field. You can specify the field to order by and whether to reverse the order:

results = db.select(User).order(\"age\", reverse=True).fetch_all()\n

This will order the results by the age field in descending order.

If you do not specify a field, the default is to order by the primary key field:

results = db.select(User).order().fetch_all()\n

This will order the results by the primary key field in ascending order.

Warning

Previously ordering was done using the direction parameter with asc or desc, but this has been deprecated in favor of using the reverse parameter. The direction parameter still works, but will raise a DeprecationWarning and will be removed in a future release.

"},{"location":"guide/properties/","title":"SqliterDB Properties","text":""},{"location":"guide/properties/#overview","title":"Overview","text":"

The SqliterDB class includes several useful read-only properties that provide insight into the current state of the database. These properties allow users to easily query key database attributes, such as the filename, whether the database is in memory, auto-commit status, and the list of tables.

"},{"location":"guide/properties/#properties","title":"Properties","text":"
  1. filename Returns the filename of the database, or None if the database is in-memory.

    Usage Example:

    db = SqliterDB(db_filename=\"test.db\")\nprint(db.filename)  # Output: 'test.db'\n
  2. is_memory Returns True if the database is in-memory, otherwise False.

    Usage Example:

    db = SqliterDB(memory=True)\nprint(db.is_memory)  # Output: True\n
  3. is_autocommit Returns True if the database is in auto-commit mode, otherwise False.

    Usage Example:

    db = SqliterDB(auto_commit=True)\nprint(db.is_autocommit)  # Output: True\n
  4. table_names Returns a list of all user-defined table names in the database. The property temporarily reconnects if the connection is closed.

    Usage Example:

    db = SqliterDB(memory=True)\ndb.create_table(User)  # Assume 'User' is a predefined model\nprint(db.table_names)  # Output: ['user']\n
"},{"location":"guide/properties/#property-details","title":"Property Details","text":""},{"location":"guide/properties/#filename","title":"filename","text":"

This property allows users to retrieve the current database filename. For in-memory databases, this property returns None, as no filename is associated with an in-memory database.

"},{"location":"guide/properties/#is_memory","title":"is_memory","text":"

This property indicates whether the database is in memory. It simplifies the check for memory-based databases, returning True for in-memory and False otherwise.

"},{"location":"guide/properties/#is_autocommit","title":"is_autocommit","text":"

This property returns whether the database is in auto-commit mode. If auto_commit is enabled, every operation is automatically committed without requiring an explicit commit() call.

"},{"location":"guide/properties/#table_names","title":"table_names","text":"

This property retrieves a list of user-defined table names from the database. It does not include system tables (sqlite_). If the database connection is closed, this property will temporarily reconnect to query the table names and close the connection afterward.

"},{"location":"guide/properties/#example","title":"Example","text":"

Here's a complete example demonstrating the use of the new properties:

from sqliter import SqliterDB\nfrom sqliter.model import BaseDBModel\n\n# Define a simple model\nclass User(BaseDBModel):\n    id: int\n    name: str\n\n# Create an in-memory database\ndb = SqliterDB(memory=True)\ndb.create_table(User)\n\n# Access properties\nprint(db.filename)        # Output: None\nprint(db.is_memory)       # Output: True\nprint(db.is_autocommit)   # Output: True (this is the default)\nprint(db.table_names)     # Output: ['user']\n
"},{"location":"guide/tables/","title":"Table Operations","text":"

All table operations work on a Pydantic Model you have defined based on BaseDBModel. You can have as many tables as you need, but each must have it's own Model defined.

"},{"location":"guide/tables/#creating-tables","title":"Creating Tables","text":"

To create a table, you simply pass your Model class to the create_table() method:

db.create_table(User)\n

Important

The Table is created regardless of the auto_commit setting.

By default, if the table already exists, it will not be created again and no error will be raised. If you want to raise an exception if the table already exists, you can set exists_ok=False:

db.create_table(User, exists_ok=False)\n

This will raise a TableCreationError if the table already exists.

There is a complementary flag force=True which will drop the table if it exists and then recreate it. This may be useful if you are changing the table structure:

db.create_table(User, force=True)\n

This defaults to False.

"},{"location":"guide/tables/#dropping-tables","title":"Dropping Tables","text":"

To drop a table completely from the database use the drop_table method

db.drop_table(User)\n

Caution

This is non-reversible and will you will lose all data in that table.

The Table is dropped regardless of the auto_commit setting.

"},{"location":"guide/transactions/","title":"Transactions","text":"

SQLiter supports transactions using Python's context manager:

with db:\n    db.insert(User(name=\"Alice\", age=30, email=\"alice@example.com\"))\n    db.insert(User(name=\"Bob\", age=35, email=\"bob@example.com\"))\n    # If an exception occurs, the transaction will be rolled back\n

Warning

Using the context manager will automatically commit the transaction at the end (unless an exception occurs), regardless of the auto_commit setting.

the close() method will also be called when the context manager exits, so you do not need to call it manually.

"},{"location":"todo/","title":"TODO","text":"

Items marked with are high priority.

"},{"location":"todo/#general-plans-and-ideas","title":"General Plans and Ideas","text":""},{"location":"todo/#housekeeping","title":"Housekeeping","text":""},{"location":"todo/#documentation","title":"Documentation","text":""},{"location":"todo/#potential-filter-additions","title":"Potential Filter Additions","text":" "}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"SQLiter","text":"

SQLiter is a lightweight Object-Relational Mapping (ORM) library for SQLite databases in Python. It provides a simplified interface for interacting with SQLite databases using Pydantic models. The only external run-time dependency is Pydantic itself.

It does not aim to be a full-fledged ORM like SQLAlchemy, but rather a simple and easy-to-use library for basic database operations, especially for small projects. It is NOT asynchronous and does not support complex queries (at this time).

The ideal use case is more for Python CLI tools that need to store data in a database-like format without needing to learn SQL or use a full ORM.

Caution

This project is still in the early stages of development and is lacking some planned functionality. Please use with caution - Classes and methods may change until a stable release is made. I'll try to keep this to an absolute minimum and the releases and documentation will be very clear about any breaking changes.

Also, structures like list, dict, set etc are not supported at this time as field types, since SQLite does not have a native column type for these. This is the next planned enhancement. These will need to be pickled first then stored as a BLOB in the database.

See the TODO for planned features and improvements.

"},{"location":"#features","title":"Features","text":""},{"location":"#license","title":"License","text":"

This project is licensed under the terms of the MIT license.

"},{"location":"installation/","title":"Installation","text":"

You can install SQLiter using whichever method you prefer or is compatible with your project setup.

With uv which is rapidly becoming my favorite tool for managing projects and virtual environments (uv is used for developing this project and in the CI):

uv add sqliter-py\n

With Poetry:

poetry add sqliter-py\n

Or with pip:

pip install sqliter-py\n
"},{"location":"installation/#optional-dependencies","title":"Optional Dependencies","text":"

Currently by default, the only external dependency is Pydantic. However, there are some optional dependencies that can be installed to enable additional features:

These can be installed using uv:

uv add 'sqliter-py[extras]'\n

With Poetry:

poetry add 'sqliter-py[extras]'\n

Or with pip:

pip install 'sqliter-py[extras]'\n
"},{"location":"license/","title":"License","text":"

This project is licensed under the terms of the MIT license below:

The MIT License (MIT)\nCopyright (c) 2024 Grant Ramsay\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\nDAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\nOTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE\nOR OTHER DEALINGS IN THE SOFTWARE.\n
"},{"location":"quickstart/","title":"Quick Start","text":"

Here's a quick example of how to use SQLiter:

from typing import Optional\n\nfrom sqliter import SqliterDB\nfrom sqliter.model import BaseDBModel\n\n# Define your model\nclass User(BaseDBModel):\n    name: str\n    age: int\n    admin: Optional[bool] = False\n\n# Create a database connection\ndb = SqliterDB(\"example.db\")\n\n# Create the table\ndb.create_table(User)\n\n# Insert a record\nuser = User(name=\"John Doe\", age=30)\nnew_user = db.insert(user)\n\n# Query records\nresults = db.select(User).filter(name=\"John Doe\").fetch_all()\nfor user in results:\n    print(f\"User: {user.name}, Age: {user.age}, Admin: {user.admin}\")\n\n# Update a record\nnew_user.age = 31\ndb.update(new_user)\n\nresults = db.select(User).filter(name=\"John Doe\").fetch_one()\n\nprint(\"Updated age:\", results.age)\n\n# Delete a record\ndb.delete(User, new_user.pk)\n

See the Guide for more detailed information on how to use SQLiter.

"},{"location":"changelog/","title":"Changelog","text":"

This is an auto-generated log of all the changes that have been made to the project since the first release, with the latest changes at the top.

This project adheres to Semantic Versioning.

"},{"location":"changelog/#070-october-31-2024","title":"0.7.0 (October 31, 2024)","text":"

New Features

Bug Fixes

Full Changelog | Diff | Patch

"},{"location":"changelog/#060-october-12-2024","title":"0.6.0 (October 12, 2024)","text":"

New Features

Bug Fixes

Full Changelog | Diff | Patch

"},{"location":"changelog/#050-september-30-2024","title":"0.5.0 (September 30, 2024)","text":"

Breaking Change!

This release removes the create_pk and primary_key attributes from the Model Meta Class. Now, an auto-incrementing primary key is created by default and the name of the primary key is always pk.

Closed Issues

Breaking Changes

Bug Fixes

Documentation

Full Changelog | Diff | Patch

"},{"location":"changelog/#040-september-27-2024","title":"0.4.0 (September 27, 2024)","text":"

New Features

Testing

Refactoring

Documentation

Full Changelog | Diff | Patch

"},{"location":"changelog/#030-september-23-2024","title":"0.3.0 (September 23, 2024)","text":"

Breaking Changes

New Features

Dependency Updates

Full Changelog | Diff | Patch

"},{"location":"changelog/#020-september-14-2024","title":"0.2.0 (September 14, 2024)","text":"

New Features

Bug Fixes

Full Changelog | Diff | Patch

"},{"location":"changelog/#011-september-12-2024","title":"0.1.1 (September 12, 2024)","text":"

Just a documentation fix - README was old version

Full Changelog | Diff | Patch

"},{"location":"changelog/#010-september-12-2024","title":"0.1.0 (September 12, 2024)","text":"

New Features

Testing

Dependency Updates

This changelog was generated using github-changelog-md by Seapagan

"},{"location":"contributing/","title":"Contributing to SQLiter","text":"

Thank you for your interest in contributing to SQLiter! We welcome all contributions, big or small.

If you are not sure where to start, please take a look at the open issues. If you have an idea for a new feature or would like to report a bug, please open a new issue.

We also welcome contributions to the documentation. If you find any errors or would like to suggest improvements, please open a new issue or submit a pull

"},{"location":"contributing/#prerequisites","title":"Prerequisites","text":"

uv can be used to actually install Python, even if you do not have it installed locally (either by system, pyenv or similar).

For example, to install Python 3.12 using uv, you can run the following command:

uv python install 3.12\n

If you already have a Python version installed, uv will use this.

"},{"location":"contributing/#getting-started","title":"Getting Started","text":"

Before you start contributing, please make sure you have read and understood our Code of Conduct and License.

To get started, follow these steps:

  1. Fork the repository and clone it to your local machine.
  2. Install the required dependencies (see next section).
  3. Create a new branch for your changes: git checkout -b my-new-feature.
  4. Make your changes and commit them: git commit -am 'Add some feature'.
  5. Push your changes to your fork: git push origin my-new-feature.
  6. Create a new pull request.
"},{"location":"contributing/#install-dependencies","title":"Install Dependencies","text":"

Run the following command to create a local virtualenv and install the required dependencies. We need the optional extras installed so the tests pass:

uv sync --all-extras\n

The .venv folder is already in the .gitignore file so will not be committed to the repository. This is where the virtual environment will be created.

You then need to activate the virtual environment:

source .venv/bin/activate\n

From here you can start working on the project. If you are using an IDE such as VSCode or PyCharm, you can set their Python interpreter setting to use the virtual environment that has just been created.

"},{"location":"contributing/#install-git-pre-commit-hooks","title":"Install Git Pre-Commit hooks","text":"

Please do this if you are intending to submit a PR. It will check commits locally before they are pushed up to the Repo.

$ pre-commit install\npre-commit installed at .git/hooks/pre-commit\n

This will ensure that all code meets the required linting standard before being committed.

"},{"location":"contributing/#run-pre-commit-manually","title":"Run pre-commit manually","text":"

You can run these checks manually on all staged files using the below command :

poe pre\n
"},{"location":"contributing/#testing","title":"Testing","text":"

We are using pytest for testing. Tests will automatically be run when you submit a pull request. You can also run them manually using the following command:

pytest\n

If you add any new features, please add tests for them. This will help us to ensure that the code is working as expected and will prevent any regressions.

"},{"location":"contributing/#changelog","title":"Changelog","text":"

The changelog is automatically generated using github-changelog-md, so please do not edit it manually.

For maintainers, there is a POE task that will run this and update the changelog file.

poe changelog\n

You would also need to add a GitHub Personal Access Token to a local config file as usual. See the section in that tools Documentation for information.

However, you should NOT include a change to the CHANGELOG.md file in any Pull Requests. This will be handled by the maintainers when a new release is made. Your GitHub username will be added to the changelog automatically beside your PR.

"},{"location":"contributing/#convenience-tasks","title":"Convenience Tasks","text":"

There are a few other convenience tasks that can be run using the poe command. These are defined in the pyproject.toml file.

Each of these tasks can have extra options added which will be passed to the underlying tool.

Run mypy on the code base in strict mode:

poe mypy\n

Format the code using ruff format:

poe format\n

Lint the code using ruff check:

poe ruff\n
"},{"location":"contributing/#documentation-tasks","title":"Documentation Tasks","text":"

These are to help with developing and updating the documentation.

"},{"location":"contributing/#guidelines","title":"Guidelines","text":"

Here are some guidelines to follow when contributing to SQLiter:

"},{"location":"contributing/#contact","title":"Contact","text":"

If you have any questions or need help with contributing, please contact me @seapagan on GitHub. You can also use the GitHub Discussions feature.

Happy contributing!

"},{"location":"guide/connecting/","title":"Connecting to the Database","text":""},{"location":"guide/connecting/#creating-a-connection","title":"Creating a Connection","text":"

To connect to a database (and create the file if it does not already exist), you create an instance of the SqliterDB class. This will automatically take care of connecting to or creating the database file.

from sqliter import SqliterDB\n\ndb = SqliterDB(\"your_database.db\")\n

The default behavior is to automatically commit changes to the database after each operation. If you want to disable this behavior, you can set auto_commit=False when creating the database connection:

db = SqliterDB(\"your_database.db\", auto_commit=False)\n

It is then up to you to manually commit changes using the commit() method. This can be useful when you want to perform multiple operations in a single transaction without the overhead of committing after each operation.

"},{"location":"guide/connecting/#using-an-in-memory-database","title":"Using an In-Memory Database","text":"

If you want to use an in-memory database, you can set memory=True when creating the database connection:

db = SqliterDB(memory=True)\n

This will create an in-memory database that is not persisted to disk. If you also specify a database name, it will be ignored.

db = SqliterDB(\"ignored.db\", memory=True)\n

The ignored.db file will not be created, and the database will be in-memory. If you do not specify a database name, and do NOT set memory=True, an exception will be raised.

Note

You can also use \":memory:\" as the database name (same as normal with Sqlite) to create an in-memory database, this is just a cleaner and more descriptive way to do it.

db = SqliterDB(\":memory:\")\n
"},{"location":"guide/connecting/#resetting-the-database","title":"Resetting the Database","text":"

If you want to reset an existing database when you create the SqliterDB object, you can pass reset=True:

db = SqliterDB(\"your_database.db\", reset=True)\n

This will effectively drop all user tables from the database. The file itself is not deleted, only the tables are dropped.

"},{"location":"guide/connecting/#database-properties","title":"Database Properties","text":"

The SqliterDB class provides several properties to access information about the database instance once it has been created. See the Properties page (next) for more details.

"},{"location":"guide/data-operations/","title":"Data Operations","text":""},{"location":"guide/data-operations/#inserting-records","title":"Inserting Records","text":"

The insert() method is used to add records to the database. You pass an instance of your model class to the method, and SQLiter will insert the record into the correct table:

user = User(name=\"Jane Doe\", age=25, email=\"jane@example.com\")\nresult = db.insert(user)\n

The result variable will contain a new instance of the model, with the primary key value set to the newly-created primary key in the database. You should use this instance to access the primary key value and other fields:

print(f\"New record inserted with primary key: {result.pk}\")\nprint(f\"Name: {result.name}, Age: {result.age}, Email: {result.email}\")\n
"},{"location":"guide/data-operations/#overriding-the-timestamps","title":"Overriding the Timestamps","text":"

By default, SQLiter will automatically set the created_at and updated_at fields to the current Unix timestamp in UTC when a record is inserted. If you want to override this behavior, you can set the created_at and updated_at fields manually before calling insert():

import time\n\nuser.created_at = int(time.time())\nuser.updated_at = int(time.time())\n

However, by default this is disabled. Any model passed to insert() will have the created_at and updated_at fields set automatically and ignore any values passed in these 2 fields.

If you want to enable this feature, you can set the timestamp_override flag to True when inserting the record:

result = db.insert(user, timestamp_override=True)\n

Important

The insert() method will raise a RecordInsertionError if you try to insert a record with a primary key that already exists in the table or if the table does not exist.

"},{"location":"guide/data-operations/#querying-records","title":"Querying Records","text":"

SQLiter provides a simple and intuitive API for querying records from the database, Starting with the select() method and chaining other methods to filter, order, limit, and offset the results:

# Fetch all users\nall_users = db.select(User).fetch_all()\n\n# Filter users\nyoung_users = db.select(User).filter(age=25).fetch_all()\n\n# Order users\nordered_users = db.select(User).order(\"age\", reverse=True).fetch_all()\n\n# Limit and offset\npaginated_users = db.select(User).limit(10).offset(20).fetch_all()\n

Important

The select() MUST come first, before any filtering, ordering, or pagination etc. This is the starting point for building your query.

See Filtering Results for more advanced filtering options.

"},{"location":"guide/data-operations/#updating-records","title":"Updating Records","text":"

You can update records in the database by modifying the fields of the model instance and then calling the update() method. You just pass the model instance to the method:

user.age = 26\ndb.update(user)\n

Important

The model you pass must have a primary key value set, otherwise an error will be raised. In other words, you use the instance of a model returned by the insert() method to update the record as it has the primary key value set, not the original instance you passed to insert().

You can also set the primary key value on the model instance manually before calling update() if you have that.

On suffescul update, the updated_at field will be set to the current Unix timestamp in UTC by default.

Warning

Unlike with the insert() method, you CANNOT override the updated_at field when calling update(). It will always be set to the current Unix timestamp in UTC. This is to ensure that the updated_at field is always accurate.

"},{"location":"guide/data-operations/#deleting-records","title":"Deleting Records","text":"

To delete a record from the database, you need to pass the model class and the primary key value of the record you want to delete:

db.delete(User, user.pk)\n
"},{"location":"guide/data-operations/#commit-your-changes","title":"Commit your changes","text":"

By default, SQLiter will automatically commit changes to the database after each operation. If you want to disable this behavior, you can set auto_commit=False when creating the database connection:

db = SqliterDB(\"your_database.db\", auto_commit=False)\n

You can then manually commit changes using the commit() method:

db.commit()\n

Note

If you are using the database connection as a context manager (see tansactions), you do not need to call commit() explicitly. The connection will be closed automatically when the context manager exits, and any changes will be committed.

"},{"location":"guide/data-operations/#close-the-connection","title":"Close the Connection","text":"

When you're done with the database connection, you should close it to release resources:

db.close()\n

Note that closing the connection will also commit any pending changes, unless auto_commit is set to False.

Note

If you are using the database connection as a context manager (see tansactions), you do not need to call close() explicitly. The connection will be closed automatically when the context manager exits, and any changes will be committed.

"},{"location":"guide/debug/","title":"Debug Logging","text":"

You can enable debug logging to see the SQL queries being executed by SQLiter. This can be useful for debugging and understanding the behavior of your application. It is disabled by default, and can be set on the SqliterDB class:

db = SqliterDB(\"your_database.db\", debug=True)\n

This will print the SQL queries to the console as they are executed. If there is an existing logger in your application then SQLiter will use that logger, otherwise it will create and use a new logger named sqliter.

"},{"location":"guide/exceptions/","title":"Exceptions","text":"

SQLiter includes several custom exceptions to handle specific errors that may occur during database operations. These exceptions inherit from a common base class, SqliterError, to ensure consistency across error messages and behavior.

"},{"location":"guide/fields/","title":"Field Control","text":""},{"location":"guide/fields/#selecting-specific-fields","title":"Selecting Specific Fields","text":"

By default, all commands query and return all fields in the table. If you want to select only specific fields, you can pass them using the fields() method:

results = db.select(User).fields([\"name\", \"age\"]).fetch_all()\n

This will return only the name and age fields for each record.

You can also pass this as a parameter to the select() method:

results = db.select(User, fields=[\"name\", \"age\"]).fetch_all()\n

Note that using the fields() method will override any fields specified in the 'select()' method.

"},{"location":"guide/fields/#excluding-specific-fields","title":"Excluding Specific Fields","text":"

If you want to exclude specific fields from the results, you can use the exclude() method:

results = db.select(User).exclude([\"email\"]).fetch_all()\n

This will return all fields except the email field.

You can also pass this as a parameter to the select() method:

results = db.select(User, exclude=[\"email\"]).fetch_all()\n
"},{"location":"guide/fields/#returning-exactly-one-explicit-field-only","title":"Returning exactly one explicit field only","text":"

If you only want to return a single field from the results, you can use the only() method:

result = db.select(User).only(\"name\").fetch_first()\n

This will return only the name field for the first record.

This is exactly the same as using the fields() method with a single field, but very specific and obvious. There is NO equivalent argument to this in the select() method. An exception WILL be raised if you try to use this method with more than one field.

"},{"location":"guide/filtering/","title":"Filtering Results","text":"

The filter() method in SQLiter supports various filter options to query records, and can be combined with other methods like order(), limit(), and offset() to build more complex queries:

result = db.select(User).filter(age__lte=30).limit(10).fetch_all()\n

It is possible to both add multiple filters in the same call, and to chain multiple filter calls together:

result = db.select(User).filter(age__gte=20, age__lte=30).fetch_all()\n
result = db.select(User).filter(age__gte=20).filter(age__lte=30).fetch_all()\n
"},{"location":"guide/filtering/#basic-filters","title":"Basic Filters","text":""},{"location":"guide/filtering/#null-checks","title":"Null Checks","text":""},{"location":"guide/filtering/#comparison-operators","title":"Comparison Operators","text":""},{"location":"guide/filtering/#list-operations","title":"List Operations","text":""},{"location":"guide/filtering/#string-operations-case-sensitive","title":"String Operations (Case-Sensitive)","text":""},{"location":"guide/filtering/#string-operations-case-insensitive","title":"String Operations (Case-Insensitive)","text":""},{"location":"guide/guide/","title":"SQLiter Overview","text":"

SQLiter is a lightweight Python library designed to simplify database operations using Pydantic models. It provides a range of functionality including table creation, CRUD operations, querying, filtering, and more. This overview briefly introduces each feature.

"},{"location":"guide/guide/#basic-setup","title":"Basic Setup","text":"

To get started, import the necessary modules and define a Pydantic model for your table:

from sqliter import SqliterDB\nfrom sqliter.model import BaseDBModel\n\nclass User(BaseDBModel):\n    name: str\n    age: int\n    email: str\n\n# Create a database connection\ndb = SqliterDB(\"example.db\")\n
"},{"location":"guide/guide/#table-creation","title":"Table Creation","text":"

SQLiter allows you to create tables automatically based on your models:

db.create_table(User)\n

This creates a table for the User model, with fields based on the attributes of the model.

"},{"location":"guide/guide/#inserting-records","title":"Inserting Records","text":"

Inserting records is straightforward with SQLiter:

user = User(name=\"John Doe\", age=30, email=\"john@example.com\")\nnew_record = db.insert(user)\n

If successful, new_record will contain a model the same as was passed to it, but including the newly-created primary key value.

"},{"location":"guide/guide/#basic-queries","title":"Basic Queries","text":"

You can easily query all records from a table:

all_users = db.select(User).fetch_all()\n
"},{"location":"guide/guide/#filtering-results","title":"Filtering Results","text":"

SQLiter allows filtering of results using various conditions:

young_users = db.select(User).filter(age__lt=30).fetch_all()\n
"},{"location":"guide/guide/#fetching-records","title":"Fetching Records","text":"

SQLiter provides methods to fetch multiple, single, or the last record in a table.

"},{"location":"guide/guide/#fetching-all-records","title":"Fetching All Records","text":"

The fetch_all() method retrieves all records from the table that match the query or filter:

all_users = db.select(User).fetch_all()\n

This returns a list of all matching records. If no record matches, an empty list is returned.

"},{"location":"guide/guide/#fetching-one-record","title":"Fetching One Record","text":"

The fetch_one() method retrieves a single record that matches the query or filter:

result = db.select(User).filter(name=\"John Doe\").fetch_one()\n

If no record is found, None is returned.

"},{"location":"guide/guide/#fetching-the-last-record","title":"Fetching the Last Record","text":"

The fetch_last() method retrieves the last record in the table, typically based on the rowid:

last_user = db.select(User).fetch_last()\n

This fetches the most recently inserted record. If no record is found, None is returned.

"},{"location":"guide/guide/#updating-records","title":"Updating Records","text":"

Records can be updated seamlessly. Simply modify the fields of the model instance and pass that to the update() method:

user.age = 31\ndb.update(user)\n
"},{"location":"guide/guide/#deleting-records","title":"Deleting Records","text":"

Deleting records is simple as well. You just need to pass the Model that defines your table and the primary key value of the record you want to delete:

db.delete(User, 1)\n

Note

You can get the primary key value from the record or model instance itself, e.g., new_record.pk and pass that as the second argument to the delete() method:

db.delete(User, new_record.pk)\n
"},{"location":"guide/guide/#advanced-query-features","title":"Advanced Query Features","text":""},{"location":"guide/guide/#ordering","title":"Ordering","text":"

SQLiter supports ordering of results by specific fields:

ordered_users = db.select(User).order(\"age\", reverse=True).fetch_all()\n
"},{"location":"guide/guide/#limiting-and-offsetting","title":"Limiting and Offsetting","text":"

Pagination is supported through limit() and offset():

paginated_users = db.select(User).limit(10).offset(20).fetch_all()\n
"},{"location":"guide/guide/#transactions","title":"Transactions","text":"

SQLiter supports transactions using Python's context manager. This ensures that a group of operations are executed atomically, meaning either all of the operations succeed or none of them are applied.

To use transactions, simply wrap the operations within a with block:

with db:\n    db.insert(User(name=\"Alice\", age=30, email=\"alice@example.com\"))\n    db.insert(User(name=\"Bob\", age=35, email=\"bob@example.com\"))\n    # If an exception occurs here, both inserts will be rolled back\n

If an error occurs within the transaction block, all changes made inside the block will be rolled back automatically.

If no errors occur, the transaction will commit and changes will be saved. The close() method will also be called when the context manager exits, so there is no need to call it manually.

"},{"location":"guide/guide/#closing-the-database","title":"Closing the Database","text":"

Always remember to close the connection when you're done:

db.close()\n

Note

If you are using the database connection as a context manager (see above), you do not need to call close() explicitly. The connection will be closed automatically when the context manager exits, and any changes will be committed.

This is a quick look at the core features of SQLiter. For more details on each functionality, see the next section.

"},{"location":"guide/models/","title":"Models","text":"

Each individual table in your database should be represented by a model. This model should inherit from BaseDBModel and define the fields that should be stored in the table. Under the hood, the model is a Pydantic model, so you can use all the features of Pydantic models, such as default values, type hints, and validation.

"},{"location":"guide/models/#defining-models","title":"Defining Models","text":"

Models are defined like this:

from sqliter.model import BaseDBModel\n\nclass User(BaseDBModel):\n    name: str\n    age: int\n    email: str\n

You can create as many Models as you need, each representing a different table in your database. The fields in the model will be used to create the columns in the table.

Important

"},{"location":"guide/models/#field-types","title":"Field Types","text":"

The following field types are currently supported:

More field types are planned for the near future, since I have the serialization/ deserialization locked in. This will include list, dict, set, and possibly JSON and Object fields.

"},{"location":"guide/models/#adding-indexes","title":"Adding Indexes","text":"

You can add indexes to your table by specifying the indexes attribute in the Meta class. This should be a list of strings, each string being the name of an existing field in the model that should be indexed.

from sqliter.model import BaseDBModel\n\nclass User(BaseDBModel):\n    name: str\n    age: int\n    email: str\n\n    class Meta:\n        indexes = [\"name\", \"email\"]\n

This is in addition to the primary key index (pk) that is automatically created.

"},{"location":"guide/models/#adding-unique-indexes","title":"Adding Unique Indexes","text":"

You can add unique indexes to your table by specifying the unique_indexes attribute in the Meta class. This should be a list of strings, each string being the name of an existing field in the model that should be indexed.

from sqliter.model import BaseDBModel\n\nclass User(BaseDBModel):\n    name: str\n    age: int\n    email: str\n\n    class Meta:\n        unique_indexes = [\"email\"]\n

These will ensure that all values in this field are unique. This is in addition to the primary key index (pk) that is automatically created.

Tip

You can specify both indexes and unique_indexes in the Meta class if you need to.

"},{"location":"guide/models/#unique-fields","title":"Unique Fields","text":"

You can also specify that a field should be all unique values by using the Unique() method from the sqliter.model module. This will ensure that all values in this field are unique.

from typing import Annotated\nfrom sqliter.model import BaseDBModel, Unique\n\nclass User(BaseDBModel):\n    name: str\n    age: int\n    email: Annotated[str, Unique()]\n

This will raise either a RecordInsertionError or RecordUpdateError if you try to insert or update a record with a duplicate value in the chosen field.

Tip

Using Annotated is optional, but without it your code wil not pass type-checking with mypy. It will work fine at runtime but is not recommended:

email: str = Unique()\n

This will give the following Mypy error:

error: Incompatible types in assignment (expression has type \"Unique\", variable has type \"str\")  [assignment]\n
"},{"location":"guide/models/#custom-table-name","title":"Custom Table Name","text":"

By default, the table name will be the same as the model name, converted to 'snake_case' and pluralized (e.g., User -> users). Also, any 'Model' suffix will be removed (e.g., UserModel -> users). To override this behavior, you can specify the table_name in the Meta class manually as below:

from sqliter.model import BaseDBModel\n\nclass User(BaseDBModel):\n    name: str\n    age: int\n    email: str\n\n    class Meta:\n        table_name = \"people\"\n

Note

The pluralization is pretty basic by default, and just consists of adding an 's' if not already there. This will fail on words like 'person' or 'child'. If you need more advanced pluralization, you can install the extras package as mentioned in the installation. Of course, you can always specify the table_name manually in this case!

"},{"location":"guide/models/#model-classmethods","title":"Model Classmethods","text":"

There are 2 useful methods you can call on your models. Note that they are Class Methods so should be called on the Model class itself, not an instance of the model:

"},{"location":"guide/models/#get_table_name","title":"get_table_name()","text":"

This method returns the actual table name for the model either specified or automatically generated. This is useful if you need to do any raw SQL queries.

table_name = User.get_table_name()\n
"},{"location":"guide/models/#get_primary_key","title":"get_primary_key()","text":"

This simply returns the name of the primary key for that table. At the moment, this will always return the string pk but this may change in the future.

primary_key = User.get_primary_key()\n
"},{"location":"guide/ordering/","title":"Ordering","text":"

For now we only support ordering by the single field. You can specify the field to order by and whether to reverse the order:

results = db.select(User).order(\"age\", reverse=True).fetch_all()\n

This will order the results by the age field in descending order.

If you do not specify a field, the default is to order by the primary key field:

results = db.select(User).order().fetch_all()\n

This will order the results by the primary key field in ascending order.

Warning

Previously ordering was done using the direction parameter with asc or desc, but this has been deprecated in favor of using the reverse parameter. The direction parameter still works, but will raise a DeprecationWarning and will be removed in a future release.

"},{"location":"guide/properties/","title":"SqliterDB Properties","text":""},{"location":"guide/properties/#overview","title":"Overview","text":"

The SqliterDB class includes several useful read-only properties that provide insight into the current state of the database. These properties allow users to easily query key database attributes, such as the filename, whether the database is in memory, auto-commit status, and the list of tables.

"},{"location":"guide/properties/#properties","title":"Properties","text":"
  1. filename Returns the filename of the database, or None if the database is in-memory.

    Usage Example:

    db = SqliterDB(db_filename=\"test.db\")\nprint(db.filename)  # Output: 'test.db'\n
  2. is_memory Returns True if the database is in-memory, otherwise False.

    Usage Example:

    db = SqliterDB(memory=True)\nprint(db.is_memory)  # Output: True\n
  3. is_autocommit Returns True if the database is in auto-commit mode, otherwise False.

    Usage Example:

    db = SqliterDB(auto_commit=True)\nprint(db.is_autocommit)  # Output: True\n
  4. table_names Returns a list of all user-defined table names in the database. The property temporarily reconnects if the connection is closed.

    Usage Example:

    db = SqliterDB(memory=True)\ndb.create_table(User)  # Assume 'User' is a predefined model\nprint(db.table_names)  # Output: ['user']\n
"},{"location":"guide/properties/#property-details","title":"Property Details","text":""},{"location":"guide/properties/#filename","title":"filename","text":"

This property allows users to retrieve the current database filename. For in-memory databases, this property returns None, as no filename is associated with an in-memory database.

"},{"location":"guide/properties/#is_memory","title":"is_memory","text":"

This property indicates whether the database is in memory. It simplifies the check for memory-based databases, returning True for in-memory and False otherwise.

"},{"location":"guide/properties/#is_autocommit","title":"is_autocommit","text":"

This property returns whether the database is in auto-commit mode. If auto_commit is enabled, every operation is automatically committed without requiring an explicit commit() call.

"},{"location":"guide/properties/#table_names","title":"table_names","text":"

This property retrieves a list of user-defined table names from the database. It does not include system tables (sqlite_). If the database connection is closed, this property will temporarily reconnect to query the table names and close the connection afterward.

"},{"location":"guide/properties/#example","title":"Example","text":"

Here's a complete example demonstrating the use of the new properties:

from sqliter import SqliterDB\nfrom sqliter.model import BaseDBModel\n\n# Define a simple model\nclass User(BaseDBModel):\n    id: int\n    name: str\n\n# Create an in-memory database\ndb = SqliterDB(memory=True)\ndb.create_table(User)\n\n# Access properties\nprint(db.filename)        # Output: None\nprint(db.is_memory)       # Output: True\nprint(db.is_autocommit)   # Output: True (this is the default)\nprint(db.table_names)     # Output: ['user']\n
"},{"location":"guide/tables/","title":"Table Operations","text":"

All table operations work on a Pydantic Model you have defined based on BaseDBModel. You can have as many tables as you need, but each must have it's own Model defined.

"},{"location":"guide/tables/#creating-tables","title":"Creating Tables","text":"

To create a table, you simply pass your Model class to the create_table() method:

db.create_table(User)\n

Important

The Table is created regardless of the auto_commit setting.

By default, if the table already exists, it will not be created again and no error will be raised. If you want to raise an exception if the table already exists, you can set exists_ok=False:

db.create_table(User, exists_ok=False)\n

This will raise a TableCreationError if the table already exists.

There is a complementary flag force=True which will drop the table if it exists and then recreate it. This may be useful if you are changing the table structure:

db.create_table(User, force=True)\n

This defaults to False.

"},{"location":"guide/tables/#dropping-tables","title":"Dropping Tables","text":"

To drop a table completely from the database use the drop_table method

db.drop_table(User)\n

Caution

This is non-reversible and will you will lose all data in that table.

The Table is dropped regardless of the auto_commit setting.

"},{"location":"guide/transactions/","title":"Transactions","text":"

SQLiter supports transactions using Python's context manager:

with db:\n    db.insert(User(name=\"Alice\", age=30, email=\"alice@example.com\"))\n    db.insert(User(name=\"Bob\", age=35, email=\"bob@example.com\"))\n    # If an exception occurs, the transaction will be rolled back\n

Warning

Using the context manager will automatically commit the transaction at the end (unless an exception occurs), regardless of the auto_commit setting.

the close() method will also be called when the context manager exits, so you do not need to call it manually.

"},{"location":"todo/","title":"TODO","text":"

Items marked with are high priority.

"},{"location":"todo/#general-plans-and-ideas","title":"General Plans and Ideas","text":""},{"location":"todo/#housekeeping","title":"Housekeeping","text":""},{"location":"todo/#documentation","title":"Documentation","text":""},{"location":"todo/#potential-filter-additions","title":"Potential Filter Additions","text":" "}]} \ No newline at end of file diff --git a/todo/index.html b/todo/index.html index 17aafac..3b5f65f 100644 --- a/todo/index.html +++ b/todo/index.html @@ -1 +1 @@ -TODO - SQLiter
Skip to content

TODO

Items marked with 🔥 are high priority.

General Plans and Ideas

  • add an 'execute' method to the main class to allow executing arbitrary SQL queries which can be chained to the 'find_first' etc methods or just used directly.
  • add a delete method to the QueryBuilder class to allow deleting single/multiple records from the database based on the query. This is in addition to the delete method in the main class which deletes a single record based on the primary key.
  • add a rollback method to the main class to allow manual rollbacks.
  • 🔥 allow adding foreign keys and relationships to each table.
  • add a migration system to allow updating the database schema without losing data.
  • add more tests where 'auto_commit' is set to False to ensure that commit is not called automatically.
  • 🔥 support structures like, list, dict, set, tuple etc. in the model. These will need to be pickled first then stored as a BLOB in the database
  • 🔥 in addition to the above (or perhaps before), add a BLOB field type to allow storing binary data, mapping to the bytes type in Python. This would be useful for storing images, files etc.
  • similarly - perhaps add a JSON field type to allow storing JSON data in a field, and an Object field type to allow storing arbitrary Python objects?
  • on update, check if the model has actually changed before sending the update to the database. This will prevent unnecessary updates and leave the updated_at correct. However, this will always require a query to the database to check the current values and so in large batch updates this could have a considerable performance impact. Probably best to gate this behind a flag.

Housekeeping

  • Tidy up the test suite - remove any duplicates, sort them into logical files (many already are), try to reduce and centralize fixtures.

Documentation

  • Nothing at the moment.

Potential Filter Additions

  • Range filter
    • __range: For selecting values within a specific range
  • Date and time filters
    • __year, __month, __day: For filtering date fields
    • __date: For filtering the date part of a datetime field
  • Regular expression filter
    • __regex: For more complex string matching
  • Numeric operations
    • __abs: Absolute value comparison
  • Boolean filters
    • __istrue, __isfalse: Explicit boolean checks
  • List field operations
    • __contains_all: Check if a list field contains all specified values
    • __contains_any: Check if a list field contains any of the specified values
  • Negation filter
    • __not: General negation for other filters
  • Distinct filter
    • __distinct: To get distinct values in a field
\ No newline at end of file +TODO - SQLiter
Skip to content

TODO

Items marked with 🔥 are high priority.

General Plans and Ideas

  • add an 'execute' method to the main class to allow executing arbitrary SQL queries which can be chained to the 'find_first' etc methods or just used directly.
  • add a delete method to the QueryBuilder class to allow deleting single/multiple records from the database based on the query. This is in addition to the delete method in the main class which deletes a single record based on the primary key.
  • add a rollback method to the main class to allow manual rollbacks.
  • 🔥 allow adding foreign keys and relationships to each table.
  • add a migration system to allow updating the database schema without losing data.
  • add more tests where 'auto_commit' is set to False to ensure that commit is not called automatically.
  • 🔥 support structures like, list, dict, set, tuple etc. in the model. These will need to be pickled first then stored as a BLOB in the database
  • 🔥 similarly - perhaps add a JSON field type to allow storing JSON data in a field, and an Object field type to allow storing arbitrary Python objects? Perhaps a Binary field type to allow storing arbitrary binary data? (just uses the existing bytes mapping but more explicit)
  • on update, check if the model has actually changed before sending the update to the database. This will prevent unnecessary updates and leave the updated_at correct. However, this will always require a query to the database to check the current values and so in large batch updates this could have a considerable performance impact. Probably best to gate this behind a flag.

Housekeeping

  • Tidy up the test suite - remove any duplicates, sort them into logical files (many already are), try to reduce and centralize fixtures.

Documentation

  • Nothing at the moment.

Potential Filter Additions

  • Range filter
    • __range: For selecting values within a specific range
  • Date and time filters
    • __year, __month, __day: For filtering date fields
    • __date: For filtering the date part of a datetime field
  • Regular expression filter
    • __regex: For more complex string matching
  • Numeric operations
    • __abs: Absolute value comparison
  • Boolean filters
    • __istrue, __isfalse: Explicit boolean checks
  • List field operations
    • __contains_all: Check if a list field contains all specified values
    • __contains_any: Check if a list field contains any of the specified values
  • Negation filter
    • __not: General negation for other filters
  • Distinct filter
    • __distinct: To get distinct values in a field
\ No newline at end of file