Metadata-Version: 2.4
Name: scruby
Version: 1.6.1
Summary: Asynchronous library for building and managing a hybrid database, by scheme of key-value.
Project-URL: Bug Tracker, https://github.com/kebasyaty/scruby/issues
Project-URL: Changelog, https://github.com/kebasyaty/scruby/blob/v1/CHANGELOG.md
Project-URL: Homepage, https://kebasyaty.github.io/scruby/
Project-URL: Repository, https://github.com/kebasyaty/scruby
Project-URL: Source, https://github.com/kebasyaty/scruby
Author-email: kebasyaty <kebasyaty@gmail.com>
License-Expression: MIT OR GPL-3.0-or-later
License-File: GPL-3.0-LICENSE
License-File: MIT-LICENSE
Keywords: async,database,scruby,store
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Operating System :: MacOS :: MacOS X
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Database
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: <4.0,>=3.12
Requires-Dist: anyio>=4.10.0
Requires-Dist: orjson>=3.11.3
Requires-Dist: phonenumbers>=9.0.13
Requires-Dist: pydantic-extra-types>=2.10.5
Requires-Dist: pydantic[email,timezone]>=2.11.7
Requires-Dist: python-dotenv>=1.2.1
Requires-Dist: xloft>=0.9.8
Description-Content-Type: text/markdown

<div align="center">
  <p align="center">
    <a href="https://github.com/kebasyaty/scruby">
      <img
        height="80"
        alt="Logo"
        src="https://raw.githubusercontent.com/kebasyaty/scruby/v1/assets/logo.svg">
    </a>
  </p>
  <p>
    <h1>Scruby (small shrub)</h1>
    <h3>Asynchronous library for building and managing a hybrid database,<br>by scheme of key-value.</h3>
    <p align="center">
      <a href="https://github.com/kebasyaty/scruby/actions/workflows/test.yml" alt="Build Status"><img src="https://github.com/kebasyaty/scruby/actions/workflows/test.yml/badge.svg" alt="Build Status"></a>
      <a href="https://kebasyaty.github.io/scruby/" alt="Docs"><img src="https://img.shields.io/badge/docs-available-brightgreen.svg" alt="Docs"></a>
      <a href="https://pypi.python.org/pypi/scruby/" alt="PyPI pyversions"><img src="https://img.shields.io/pypi/pyversions/scruby.svg" alt="PyPI pyversions"></a>
      <a href="https://pypi.python.org/pypi/scruby/" alt="PyPI status"><img src="https://img.shields.io/pypi/status/scruby.svg" alt="PyPI status"></a>
      <a href="https://pypi.python.org/pypi/scruby/" alt="PyPI version fury.io"><img src="https://badge.fury.io/py/scruby.svg" alt="PyPI version fury.io"></a>
      <br>
      <a href="https://pyrefly.org/" alt="Types: Pyrefly"><img src="https://img.shields.io/badge/types-Pyrefly-FFB74D.svg" alt="Types: Pyrefly"></a>
      <a href="https://docs.astral.sh/ruff/" alt="Code style: Ruff"><img src="https://img.shields.io/badge/code%20style-Ruff-FDD835.svg" alt="Code style: Ruff"></a>
      <a href="https://pypi.org/project/scruby"><img src="https://img.shields.io/pypi/format/scruby" alt="Format"></a>
      <a href="https://pepy.tech/projects/scruby"><img src="https://static.pepy.tech/badge/scruby" alt="PyPI Downloads"></a>
      <a href="https://github.com/kebasyaty/scruby/blob/main/MIT-LICENSE" alt="License: MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
      <a href="https://github.com/kebasyaty/scruby/blob/main/GPL-3.0-LICENSE" alt="License: GPL v3"><img src="https://img.shields.io/badge/License-GPLv3-blue.svg" alt="License: GPL v3"></a>
    </p>
    <p align="center">
        The library uses fractal-tree addressing and
        <br>
        the search for documents based on the effect of a quantum loop.
        <br>
        The database consists of collections.
        <br>
        The maximum size of the one collection is <b>16**8=4294967296</b> branches,
        <br>
        each branch can store one or more keys.
        <br>
        The value of any key in collection can be obtained maximum in <b>8</b> steps,
        <br>
        thereby achieving high performance.
        <br>
        The effectiveness of the search for documents based on a quantum loop,
        <br>
        requires a large number of processor threads.
    </p>
  </p>
</div>

##

<br>

<img src="https://raw.githubusercontent.com/kebasyaty/scruby/v1/assets/attention.svg" alt="Attention">
<p>
<b>Parameter `ScrubyConfig.HASH_REDUCE_LEFT`:</b>
<br>
0 = 4294967296 branches in collection -> It is recommended to work with documents only using keys.
<br>
2 = 16777216 branches in collection -> It is recommended to work with documents only using keys.
<br>
4 = 65536 branches in collection -> It is recommended to work with documents only using keys.
<br>
6 = 256 branches in collection (by default) -> Use all standard tools and plugins.
</p>

<br>
<br>
<br>

[![List of plugins](https://raw.githubusercontent.com/kebasyaty/scruby/v1/assets/links/plugins.svg "List of plugins")](https://github.com/kebasyaty/scruby/blob/v1/PLUGINS.md "List of plugins")

[![Documentation](https://raw.githubusercontent.com/kebasyaty/scruby/v1/assets/links/documentation.svg "Documentation")](https://kebasyaty.github.io/scruby/ "Documentation")

[![Requirements](https://raw.githubusercontent.com/kebasyaty/scruby/v1/assets/links/requirements.svg "Requirements")](https://github.com/kebasyaty/scruby/blob/v1/REQUIREMENTS.md "Requirements")

## Installation

```shell
uv add scruby
```

## Run

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

## Usage

[![Examples](https://raw.githubusercontent.com/kebasyaty/scruby/v1/assets/links/examples.svg "Examples")](https://kebasyaty.github.io/scruby/latest/pages/usage/ "Examples")

```python
"""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
from pprint import pprint as pp


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/Create a User collection
    user_coll = await Scruby.collection(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
    user = await user_coll.get_doc("+447986123456")
    pp(user)
    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")

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


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

```python
"""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 Scruby, ScrubyModel, ScrubyConfig
from pprint import pprint as pp


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/Create a Phone collection
    phone_coll = await Scruby.collection(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 = await phone_coll.find_one(
        filter_fn=lambda doc: doc.brand == "Samsung",
    )
    if phone_details is not None:
        pp(phone_details)
    else:
        print("No Phone!")

    # Find phone by model
    phone_details: Phone | None = await phone_coll.find_one(
        filter_fn=lambda doc: doc.model == "Galaxy A26",
    )
    if phone_details is not None:
        pp(phone_details)
    else:
        print("No Phone!")

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


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

```python
"""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 Scruby, ScrubyModel, ScrubyConfig
from pprint import pprint as pp


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/Create a Car collection
    car_coll = await Scruby.collection(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 = await car_coll.find_many()
    if car_list is not None:
        pp(car_list)
    else:
        print("No cars!")

    # Find cars by brand and year
    car_list: list[Car] | None = await car_coll.find_many(
        filter_fn=lambda doc: doc.brand == "Mazda" and doc.year == 2025,
    )
    if car_list is not None:
        pp(car_list)
    else:
        print("No cars!")

    # Pagination
    car_list: list[Car] | None = await car_coll.find_many(
        filter_fn=lambda doc: doc.brand == "Mazda",
        limit_docs=5,
        page_number=2,
    )
    if car_list is not None:
        pp(car_list)
    else:
        print("No cars!")

    # Sorting
    car_list: list[Car] | None = await car_coll.find_many(
        filter_fn=lambda doc: doc.brand == "Mazda",
        sort_fn=lambda doc: (doc.brand, doc.updated_at),
        sort_reverse=True,
    )
    if car_list is not None:
        pp(car_list)
    else:
        print("No cars!")

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


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

<br>

[![Changelog](https://raw.githubusercontent.com/kebasyaty/scruby/v1/assets/links/changelog.svg "Changelog")](https://github.com/kebasyaty/scruby/blob/v1/CHANGELOG.md "Changelog")

[![MIT](https://raw.githubusercontent.com/kebasyaty/scruby/v1/assets/links/mit.svg "MIT")](https://github.com/kebasyaty/scruby/blob/main/MIT-LICENSE "MIT")

[![GPL-3.0](https://raw.githubusercontent.com/kebasyaty/scruby/v1/assets/links/gpl-3.0-or-later.svg "GPL-3.0")](https://github.com/kebasyaty/scruby/blob/main/GPL-3.0-LICENSE "GPL-3.0")
