Skip to main content

Asynchronous library for building and managing a hybrid database, by scheme of key-value.

Project description

Logo

Scruby

Scruby - Small Shrub
Asynchronous library for building and managing a hybrid database,
by scheme of key-value.

Build Status Docs PyPI pyversions PyPI status PyPI version fury.io
Types: Pyrefly Code style: Ruff Format PyPI Downloads License: MIT License: GPL v3

The library uses fractal-tree addressing and
the search for documents based on the effect of a quantum loop.

The size of each collection is 16|256|4096|4294967296 branches,
each branch can store one or more keys.

The value of any key in collection can be obtained in 1-8 steps,
thereby achieving high performance.

The effectiveness of the search for documents based on a quantum loop,
requires a large number of processor threads.


Attention

Parameter `ScrubyConfig.HASH_REDUCE_LEFT - Scruby.run(hash_reduce_left = 7)`:
7 = 16 branches in collection (is default) -> Docs: ~16000+, RAM: ~2G+, CPU: ~2+ (for development).
6 = 256 branches in collection -> Docs: ~256000+, RAM: ~4G+, CPU: ~2+ (for small projects).
5 = 4096 branches in collection -> Docs: ~4096000+, RAM: ~6G+, CPU: ~4+ (for large projects).
0 = 4294967296 branches in collection -> Docs: ~4,294967296×10¹²+, RAM: ~2G+, CPU: ~2+ (access only by keys).

If you notice the production server slowing down,
you will need to add RAM and CPU.




List of plugins

Documentation

Requirements

Installation

uv add scruby

Run

# Run Development:
uv run python main.py
# Run Production:
uv run python -OOP main.py

Usage

Examples

"""Working with keys."""

import anyio
from datetime import datetime
from zoneinfo import ZoneInfo
from typing import Annotated
from pydantic import EmailStr, Field
from pydantic_extra_types.phone_numbers import PhoneNumber, PhoneNumberValidator
from scruby import Scruby, ScrubyModel, ScrubyConfig


class User(ScrubyModel):
    """User model."""
    first_name: str = Field(strict=True)
    last_name: str = Field(strict=True)
    birthday: datetime = Field(strict=True)
    email: EmailStr = Field(strict=True)
    phone: Annotated[PhoneNumber, PhoneNumberValidator(number_format="E164")] = Field(frozen=True)
    # key is always at bottom
    key: str = Field(
        strict=True,
        frozen=True,
        default_factory=lambda data: data["phone"],
    )


async def main() -> None:
    """Example."""
    # Activate database.
    Scruby.run()

    # Get access to the collection
    user_coll = Scruby(User)

    # Create user
    user = User(
        first_name="John",
        last_name="Smith",
        birthday=datetime(1970, 1, 1, tzinfo=ZoneInfo("UTC")),
        email="John_Smith@gmail.com",
        phone="+447986123456",
    )

    # Add user to collection
    await user_coll.add_doc(user)

    # Update user data in a collection
    await user_coll.update_doc(user)

    # Get user details
    await user = user_coll.get_doc("+447986123456")
    await user_coll.get_doc("key missing")  # => None

    # Check for the presence of a key in the collection
    await user_coll.has_key("+447986123456")  # => True

    # Delete a document by key
    await user_coll.delete_doc("+447986123456")

    # Get collection name
    user_coll.collection_name()  # => User

    # Get collection list
    coll_list = Scruby.collection_list()  # => ["User"]

    # Get the number of documents in the collection from metadata
    await user_coll.estimated_document_count()  # => 1

    # Get the number of documents comparable to the filter
    user_coll.count_documents(filter_fn=lambda doc: doc.first_name == "John") == 1

    # Clear collection
    Scruby.clear_collection("User")

    # Full database deletion
    # Hint: The main purpose is tests
    Scruby.napalm()


if __name__ == "__main__":
    anyio.run(main)
"""Find one document matching the filter.

The search is based on the effect of a quantum loop.
The search effectiveness depends on the number of processor threads.
"""

import anyio
from pydantic import Field
from scruby import ReturnType, Scruby, ScrubyConfig, ScrubyModel


class Phone(ScrubyModel):
    """Phone model."""
    brand: str = Field(strict=True, frozen=True)
    model: str = Field(strict=True, frozen=True)
    screen_diagonal: float = Field(strict=True)
    matrix_type: str = Field(strict=True)
    # key is always at bottom
    key: str = Field(
        strict=True,
        frozen=True,
        default_factory=lambda data: f"{data['brand']}:{data['model']}",
    )


async def main() -> None:
    """Example."""
    # Activate database.
    Scruby.run()

    # Get access to the collection
    phone_coll = Scruby(Phone)

    # Create phone
    phone = Phone(
        brand="Samsung",
        model="Galaxy A26",
        screen_diagonal=6.7,
        matrix_type="Super AMOLED",
    )

    # Add phone to collection
    await phone_coll.add_doc(phone)

    # Find phone by brand
    phone_details: Phone | None = phone_coll.find_one(
        filter_fn=lambda doc: doc.brand == "Samsung",
    )

    # Find phone by model
    phone_details: Phone | None = phone_coll.find_one(
        filter_fn=lambda doc: doc.model == "Galaxy A26",
    )

    # Return phone in JSON format
    phone_details: str | None = phone_coll.find_one(
        filter_fn=lambda doc: doc.model == "Galaxy A26",
        return_type=ReturnType.JSON,
    )

    # Return phone in Dictionary format
    phone_details: dict | None = phone_coll.find_one(
        filter_fn=lambda doc: doc.model == "Galaxy A26",
        return_type=ReturnType.DICT,
    )

    # Full database deletion
    # Hint: The main purpose is tests
    Scruby.napalm()


if __name__ == "__main__":
    anyio.run(main)
"""Find many documents matching the filter.

The search is based on the effect of a quantum loop.
The search effectiveness depends on the number of processor threads.
"""

import anyio
from typing import Annotated
from pydantic import Field
from scruby import ReturnType, Scruby, ScrubyConfig, ScrubyModel


class Car(ScrubyModel):
    """Car model."""
    brand: str = Field(strict=True, frozen=True)
    model: str = Field(strict=True, frozen=True)
    year: int = Field(strict=True)
    power_reserve: int = Field(strict=True)
    # key is always at bottom
    key: str = Field(
        strict=True,
        frozen=True,
        default_factory=lambda data: f"{data['brand']}:{data['model']}",
    )


async def main() -> None:
    """Example."""
    # Activate database.
    Scruby.run()

    # Get access to the collection
    car_coll = Scruby(Car)

    # Create cars
    for num in range(1, 10):
        car = Car(
            brand="Mazda",
            model=f"EZ-6 {num}",
            year=2025,
            power_reserve=600,
        )
        await car_coll.add_doc(car)

    # Find all cars
    car_list: list[Car] | None = car_coll.find_many()

    # Find cars by brand and year
    car_list: list[Car] | None = car_coll.find_many(
        filter_fn=lambda doc: doc.brand == "Mazda" and doc.year == 2025,
    )

    # Pagination
    car_list: list[Car] | None = car_coll.find_many(
        filter_fn=lambda doc: doc.brand == "Mazda",
        limit_docs=5,
        page_number=2,
    )

    # Sorting
    car_list: list[Car] | None = car_coll.find_many(
        filter_fn=lambda doc: doc.brand == "Mazda",
        sort_fn=lambda doc: (doc.brand, doc.updated_at),
        sort_reverse=True,
    )

    # Return cars in JSON format
    car_list: str | None = car_coll.find_many(
        filter_fn=lambda doc: doc.brand == "Mazda",
        return_type=ReturnType.JSON,
    )

    # Return cars in Dictionary format
    car_list: list[dict] | None = car_coll.find_many(
        filter_fn=lambda doc: doc.brand == "Mazda",
        return_type=ReturnType.DICT,
    )

    # Update one or more documents matching the filter
    count_updated = await car_coll.update_many(
        new_data={"brand": "BMW"},
        filter_fn=lambda doc: doc.brand == "Mazda",
    )

    # Delete one or more documents matching the filter
    count_deleted = await car_coll.delete_many(
        filter_fn=lambda doc: doc.brand == "BMW",
    )

    # Full database deletion
    # Hint: The main purpose is tests
    Scruby.napalm()


if __name__ == "__main__":
    anyio.run(main)

Changelog

MIT

GPL-3.0

Project details


Release history Release notifications | RSS feed

Download files

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

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

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

scruby-2.4.3-py3-none-any.whl (39.2 kB view details)

Uploaded Python 3

File details

Details for the file scruby-2.4.3-py3-none-any.whl.

File metadata

  • Download URL: scruby-2.4.3-py3-none-any.whl
  • Upload date:
  • Size: 39.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Fedora Linux","version":"44","id":"","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for scruby-2.4.3-py3-none-any.whl
Algorithm Hash digest
SHA256 affe56bc6e62a2e894d171e5428ed9f4a980b8c11b7b217cccd5ebdc1ab59fca
MD5 bd3da330b7c2ab4b825668e05e8bd7bf
BLAKE2b-256 34af276c2d5cb479a6ce458a7d456b06d83aa30ae44174a2fb7b54f4d9e83dd3

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