Skip to main content

NQLStore, a simple CRUD store python library for `any query launguage` (or in short `nql`).

Reason this release was yanked:

The package was unusable when installed with only one of the extras 'sql', 'redis', or 'mongo' due to import errors

Project description

NQLStore

PyPI version CI

NQLStore, a simple CRUD store python library for any query launguage (or in short nql)


NQLStore provides an oversimplified API for the mundane things of creating, reading, updating, and deleting data models that are persisted to any SQL- or NoSQL- database.

In total, all we need are four methods and that is it.

Supported databases include:

  • Relational databases like:

    • SQLite
    • PostgreSQL
    • MySQL
  • NoSQL databases like:

    • Redis
    • MongoDB

If you like our simple API, you can even easily extend it to support your favourite database technology.

Dependencies

Quick Start

Install NQLStore from Pypi

Install NQLStore from pypi, with any of the options: sql, mongo, redis, all.

pip install nqlstore

Swap out your object mapping (OM) package imports with nqlstore

In your python modules, define your data models as you would define them with your favourite OM package. The only difference is the package you import them from.

Here are examples of OM packages to substitute.

SQL (use SQLModel models)

# models.py

from nqlstore.sql import Field, SQLModel 

class Library(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    address: str 
    name: str 
    
class Book(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    title: str 
    library_id: int = Field(default=None, foreign_key="library.id")

Redis (use RedisOM models)

Take note that JsonModel, EmbeddedJsonModel require RedisJSON, while queries require RedisSearch to be loaded You need to install redis-stack or load the modules manually

# models.py
from typing import List 

from nqlstore.redis import Field, JsonModel, EmbeddedJsonModel

class Book(EmbeddedJsonModel):
    title: str = Field(index=True)
    
class Library(JsonModel):
    address: str 
    name: str = Field(index=True)
    books: List[Book]

Mongo (use Beanie)

# models.py

from nqlstore.mongo import Document, Indexed
    
class Library(Document):
    address: str
    name: str 


class Book(Document):
    title: Indexed(str) 
    library_id: str

Initialize your store

Initialize the store that is to host your models. Similar to how you imported models from specific packages in nqlstore, import stores from the appropriately named modules innqlstore.

Here are examples for the different database technologies.

SQL

Migrations are outside the scope of this package

# main.py

from nqlstore.sql import SQLStore
from .models import Book, Library

if __name__ == "__main__":
    store = SQLStore(uri="sqlite+aiosqlite:///database.db")
    store.register([
        Library,
        Book,
    ])

Redis

# main.py

from nqlstore.redis import RedisStore
from .models import Book, Library

if __name__ == "__main__":
    store = RedisStore(uri="rediss://username:password@localhost:6379/0")
    store.register([
        Library,
        Book,
    ])

Mongo

# main.py

from nqlstore.mongo import MongoStore
from .models import Book, Library

if __name__ == "__main__":
    store = MongoStore(uri="mongodb://localhost:27017", database="testing")
    store.register([
        Library,
        Book,
    ])

Use your models in your application

In the rest of you application use the four class methods available on the models.
Filtering follows the MongoDb-style

However, for more complex queries, one can also pass in querying styles native to the type of the database, alongside the MongoBD-style querying. The two queries would be merged as AND queries.

Or one can simply ignore the MongoDB-style querying and stick to the native querying.

The available querying formats include:

Insert

Inserting new items in a store, call store.insert() method.

new_libraries = await store.insert(Library, [{}, {}])

Find

Finding items in a store, call the store.find() method.

The key-word arguments include:

  • skip (int) - number of records to ignore at the top of the returned results; default is 0.
  • limit (int | None) - maximum number of records to return; default is None.

The querying format is as described above

SQL
MongoDB-style:
libraries = await store.find(
    Library, query={"name": {"$eq": "Hairora"}, "address" : {"$ne": "Buhimba"}}
)
Native-style only:
libraries = await store.find(
    Library, Library.name == "Hairora", Library.address != "Buhimba"
)
Hybrid
libraries = await store.find(
    Library, Library.name == "Hairora", query={"address" : {"$ne": "Buhimba"}}
)
Redis
MongoDB-style:
libraries = await store.find(
    Library, query={"name": {"$eq": "Hairora"}, "address" : {"$ne": "Buhimba"}}
)
Native-style only:
libraries = await store.find(
    Library, (Library.name == "Hairora") & (Library.address != "Buhimba")
)
Hybrid
libraries = await store.find(
    Library, (Library.name == "Hairora"), query={"address" : {"$ne": "Buhimba"}}
)
Mongo
libraries = await store.find(
    Library, {"name": "Hairora", "address": {"$ne": "Buhimba"}}
)

Update

Updating items in a store, call the store.update() method.

The method returns the newly updated records.
The filters follow the same style as that used when querying as shown above.
Similarly, updates are different for each type of database technology as alluded to earlier.

SQL updates are just dictionaries of the new field values
MongoDB-style:
libraries = await store.update(
    Library, 
    query={"name": {"$eq": "Hairora"}, "address" : {"$ne": "Buhimba"}},
    updates={"name": "Foo"},
)
Native-style only:
libraries = await store.update(
    Library, 
    Library.name == "Hairora", Library.address != "Buhimba", 
    updates={"name": "Foo"},
)
Hybrid
libraries = await store.update(
    Library, 
    Library.name == "Hairora", query={"address" : {"$ne": "Buhimba"}},
    updates={"name": "Foo"},
)
Redis updates are just dictionaries of the new field values
MongoDB-style:
libraries = await store.update(
    Library, 
    query={"name": {"$eq": "Hairora"}, "address" : {"$ne": "Buhimba"}},
    updates={"name": "Foo"},
)
Native-style only:
libraries = await store.update(
    Library, 
    (Library.name == "Hairora") & (Library.address != "Buhimba"), 
    updates={"name": "Foo"},
)
Hybrid
libraries = await store.update(
    Library, 
    (Library.name == "Hairora"), 
    query={"address" : {"$ne": "Buhimba"}},
    updates={"name": "Foo"},
)
Mongo updates are MongoDB-style update dicts
libraries = await store.update(
    Library,
    {"name": "Hairora", "address": {"$ne": "Buhimba"}},
    updates={"$set": {"name": "Foo"}},
)

Delete

Deleting items in a store, call the store.delete() method.

The filters follow the same style as that used when reading as shown above.

SQL
MongoDB-style:
libraries = await store.delete(
    Library, query={"name": {"$eq": "Hairora"}, "address" : {"$ne": "Buhimba"}}
)
Native-style only:
libraries = await store.delete(
    Library, Library.name == "Hairora", Library.address != "Buhimba"
)
Hybrid
libraries = await store.delete(
    Library, Library.name == "Hairora", query={"address" : {"$ne": "Buhimba"}}
)
Redis
MongoDB-style:
libraries = await store.delete(
    Library, query={"name": {"$eq": "Hairora"}, "address" : {"$ne": "Buhimba"}}
)
Native-style only:
libraries = await store.delete(
    Library, (Library.name == "Hairora") & (Library.address != "Buhimba")
)
Hybrid
libraries = await store.delete(
    Library, (Library.name == "Hairora"), query={"address" : {"$ne": "Buhimba"}}
)
Mongo
libraries = await store.delete(
  Library, {"name": "Hairora", "address": {"$ne": "Buhimba"}}
)

Contributions

Contributions are welcome. The docs have to maintained, the code has to be made cleaner, more idiomatic and faster, and there might be need for someone else to take over this repo in case I move on to other things. It happens!

When you are ready, look at the CONTRIBUTIONS GUIDELINES

License

Copyright (c) 2025 Martin Ahindura
Licensed under the MIT License

Gratitude

"In that day you will ask in My name. I am not saying that I will ask the Father on your behalf. No, the Father himself loves you because you have loved Me and have believed that I came from God."

-- John 16: 26-27

All glory be to God

Buy Me A Coffee

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

nqlstore-0.0.1.tar.gz (25.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

nqlstore-0.0.1-py3-none-any.whl (15.7 kB view details)

Uploaded Python 3

File details

Details for the file nqlstore-0.0.1.tar.gz.

File metadata

  • Download URL: nqlstore-0.0.1.tar.gz
  • Upload date:
  • Size: 25.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.10.16

File hashes

Hashes for nqlstore-0.0.1.tar.gz
Algorithm Hash digest
SHA256 720ba5a035aa6fe16f0739a702764d3a86361293c6a8f06f4ea1fb85cd9d7a79
MD5 191682d8f9b5168fc0e325aac5a3292b
BLAKE2b-256 2971fdc712c1c00dae67388c21b5d0d31805f6be2018e0da8317a510c3a67bd7

See more details on using hashes here.

File details

Details for the file nqlstore-0.0.1-py3-none-any.whl.

File metadata

  • Download URL: nqlstore-0.0.1-py3-none-any.whl
  • Upload date:
  • Size: 15.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.10.16

File hashes

Hashes for nqlstore-0.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 2fcab12f75de05612b771feaa2bbae5e701709f1de2400bd9a2f6047f70708d9
MD5 2193363af5ac174b50f457ffe4ae5e59
BLAKE2b-256 1558aa9145b31041b695d7d820c79d962a3812d6e8e1c46b12404fb28ae5feee

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page