Skip to main content

ORM-like API MongoDB for Python language.

Project description

Logo

ramifice

ORM-like API MongoDB for Python language.

Build Status Docs PyPI pyversions PyPI status PyPI version fury.io
GitHub issues PyPI Downloads GitHub license GitHub repository

Ramifice is built around PyMongo.
For simulate relationship Many-to-One and Many-to-Many,
a simplified alternative (Types of selective fields with dynamic addition of elements) is used.
The project is more concentrated for web development or for applications with a graphic interface.

MongoDB
Supports MongoDB 3.6, 4.0, 4.2, 4.4, 5.0, 6.0, 7.0, and 8.0.
For more information see PyMongo.

Project Status

Documentation

Online browsable documentation is available at https://kebasyaty.github.io/ramifice/.

Requirements

View the list of requirements.

Installation

  1. Install MongoDB (if not installed):
    Fedora Ubuntu Windows

  2. Run:

# Fedora:
sudo dnf install gettext
gettext --version
# Ubuntu:
sudo apt install gettext
gettext --version
# Windows:
https://mlocati.github.io/articles/gettext-iconv-windows.html
gettext --version

cd project_name
uv add ramifice
  1. Add config and public directories in root of your project:
    Download config directory
    Download public directory

Usage

It is recommended to look at examples here.

import asyncio
import pprint
from datetime import datetime

from pymongo import AsyncMongoClient
from ramifice import model, translations
from ramifice.fields import DateField, EmailField, ImageField, PasswordField, TextField
from ramifice.migration import Monitor


@model(service_name="Accounts")
class User:
    def fields(self):
        # For custom translations.
        gettext = translations.gettext
        # ngettext = translations.ngettext
        self.avatar = ImageField(
            label=gettext("Avatar"),
            default="public/media/default/no-photo.png",
            # Available 4 sizes from lg to xs or None.
            # Hint: By default = None
            thumbnails={"lg": 512, "md": 256, "sm": 128, "xs": 64},
            # True - high quality and low performance for thumbnails.
            # Hint: By default = False
            high_quality=True,
            # The maximum size of the original image in bytes.
            # Hint: By default = 2 MB
            max_size=524288 # 0.5 MB = 524288 Bytes (in binary)
        )
        self.username = TextField(
            label=gettext("Username"),
            required=True,
            unique=True,
        )
        self.first_name = TextField(label=gettext("First name"), required=True)
        self.last_name = TextField(
            label=gettext("Last name"),
            required=True,
        )
        self.email = EmailField(
            label=gettext("Email"),
            required=True,
            unique=True,
        )
        self.birthday = DateField(label=gettext("Birthday"))
        self.password = PasswordField(label=gettext("Password"))
        self.сonfirm_password = PasswordField(
            label=gettext("Confirm password"),
            # If true, the value of this field is not saved in the database.
            ignored=True,
        )

    async def add_validation(self) -> dict[str, str]:
        """Additional validation of fields."""
        error_map: dict[str, str] = {}
        if self.password != self.сonfirm_password:
            error_map["password"] = "Passwords do not match!"
        return error_map


async def main():
    client = AsyncMongoClient()

    await Monitor(
        database_name="test_db",
        mongo_client=client,
    ).migrat()

    # If you need to change the language of translation.
    # translations.change_locale("ru")

    user = User()
    user.username.value = "pythondev"
    user.avatar.from_path("public/media/default/no-photo.png")
    user.first_name.value = "John"
    user.last_name.value = "Smith"
    user.email.value = "John_Smith@gmail.com"
    user.birthday.value = datetime(2000, 1, 25)
    user.password.value = "12345678"
    user.сonfirm_password.value = "12345678"

    if not await user.save():
        user.print_err()  # Convenient to use during development.

    doc_count = await User.estimated_document_count()
    print(f"Document count: {doc_count}")  # => 1

    print("User details:")
    user_details = await User.find_one_to_raw_doc({"_id": user._id.value})
    pprint.pprint(user_details)

    await user.delete(remove_files=False)
    doc_count = await User.estimated_document_count()
    print(f"Document count: {doc_count}")  # => 0

    await client.close()


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

How to create custom translations ?

from ramifice import translations

translations.DEFAULT_LOCALE = "en"  # For Ramifice by default = "en"
translations.LANGUAGES = ["en", "ru"]  # For Ramifice by default = ["en", "ru"]
cd project_name
# Add your custom translations:
uv run pybabel extract -o config/translations/custom.pot src
uv run pybabel init -i config/translations/custom.pot -d config/translations/custom -l en
uv run pybabel init -i config/translations/custom.pot -d config/translations/custom -l ru
...

# Hint: Do not forget to add translations for new languages.
uv run pybabel compile -d config/translations/custom

# Update your custom translations:
uv run pybabel extract -o config/translations/custom.pot src
uv run pybabel update -i config/translations/custom.pot -d config/translations/custom
# Hint: Do not forget to check the translations for existing languages.
uv run pybabel compile -d config/translations/custom

How to add new languages ​​to Ramifice ?

from ramifice import translations

translations.DEFAULT_LOCALE = "en"  # For Ramifice by default = "en"
translations.LANGUAGES = ["en", "ru", "de", "de_ch"]  # For Ramifice by default = ["en", "ru"]
cd project_name
# Example:
uv run pybabel init -i config/translations/ramifice.pot -d config/translations/ramifice -l de
uv run pybabel init -i config/translations/ramifice.pot -d config/translations/ramifice -l de_ch
...

# Hint: Do not forget to add translations for new languages.
uv run pybabel compile -d config/translations/ramifice

# Update translations to Ramifice:
uv run pybabel extract -o config/translations/ramifice.pot ramifice
uv run pybabel update -i config/translations/ramifice.pot -d config/translations/ramifice
# Hint: Do not forget to check the translations for existing languages.
uv run pybabel compile -d config/translations/ramifice

See more examples here.

Model Parameters

See the documentation here.

( only service_name is a required parameter )
Parameter Default Description
service_name no Examples: Accounts | Smartphones | Washing machines | etc ...
fixture_name None The name of the fixture in the config/fixtures directory (without extension).
Examples: SiteSettings | AppSettings | etc ...
db_query_docs_limit 1000 limiting query results.
is_migrate_model True Set to False if you do not need to migrate the Model to the database.
This can be use to validate a web forms - Search form, Contact form, etc.
is_create_doc True Can a Model create new documents in a collection?
Set to False if you only need one document in the collection and the Model is using a fixture.
is_update_doc True Can a Model update documents in a collection?
is_delete_doc True Can a Model remove documents from a collection?

Example:

@model(
    service_name="ServiceName",
    fixture_name="FixtureName",
    db_query_docs_limit=1000,
    is_migrate_model=True,
    is_create_doc = True,
    is_update_doc = True,
    is_delete_doc = True,
)
class User:
    def fields(self):
        self.username = TextField(
            label=gettext("Username"),
            required=True,
            unique=True,
        )

Class methods

List of frequently used methods:

# Gets an estimate of the count of documents in a collection using collection metadata.
count: int = await User.estimated_document_count()

# Gets an estimate of the count of documents in a collection using collection metadata.
q_filter = {"first_name": "John"}
count: int = await User.count_documents(q_filter)

# Runs an aggregation framework pipeline.
from bson.bson import BSON
pipeline = [
    {"$unwind": "$tags"},
    {"$group": {"_id": "$tags", "count": {"$sum": 1}}},
    {"$sort": BSON([("count", -1), ("_id", -1)])},
]
docs = await User.aggregate(pipeline)

# Finds the distinct values for a specified field across a single collection.
q_filter = "key_name"
values = await User.distinct(q_filter)

# Get collection name.
name = await User.collection_name()

# The full name is of the form database_name.collection_name.
name = await User.collection_full_name()

# Get AsyncBatabase for the current Model.
database = await User.database()

# Get AsyncCollection for the current Model.
collection = await User.collection()

# Find a single document.
q_filter = {"email": "John_Smith@gmail.com"}
mongo_doc = await User.find_one(q_filter)

# Find a single document and converting to raw document.
q_filter = {"email": "John_Smith@gmail.com"}
raw_doc = await User.find_one_to_raw_doc(q_filter)

# Find a single document and convert it to a Model instance.
q_filter = {"email": "John_Smith@gmail.com"}
user = await User.find_one_to_instance(q_filter)

# Find a single document and convert it to a JSON string.
q_filter = {"email": "John_Smith@gmail.com"}
json = await User.find_one_to_json(q_filter)

# Find a single document and delete it.
q_filter = {"email": "John_Smith@gmail.com"}
delete_result = await User.delete_one(q_filter)

# Find a single document and delete it, return original.
q_filter = {"email": "John_Smith@gmail.com"}
mongo_doc = await User.find_one_and_delete(q_filter)

# Find documents.
q_filter = {"first_name": "John"}
mongo_docs = await User.find_many(q_filter)

# Find documents and convert to a raw documents.
q_filter = {"first_name": "John"}
raw_docs = await User.find_many_to_raw_docs(q_filter)

# Find documents and convert to a json string.
q_filter = {"email": "John_Smith@gmail.com"}
json = await User.find_many_to_json(q_filter)

# Find documents matching with Model.
q_filter = {"email": "John_Smith@gmail.com"}
delete_result = await User.delete_many(q_filter)

# Creates an index on this collection.
from pymongo import ASCENDING
keys = [("email", ASCENDING)]
result: str = await User.create_index(keys, name="idx_email")

# Drops the specified index on this collection.
User.drop_index("idx_email")

# Create one or more indexes on this collection.
from pymongo import ASCENDING, DESCENDING
index_1 = IndexModel([("username", DESCENDING), ("email", ASCENDING)], name="idx_username_email")
index_2 = IndexModel([("first_name", DESCENDING)], name="idx_first_name")
result: list[str] = await User.create_indexes([index_1, index_2])

# Drops all indexes on this collection.
User.drop_index()

# Get information on this collection’s indexes.
result = await User.index_information()

# Get a cursor over the index documents for this collection.
async for index in await User.list_indexes():
    print(index)

# Units Management.
# Management for `choices` parameter in dynamic field types.
# Units are stored in a separate collection.
from ramifice.types import Unit
unit = Unit(
  field="field_name",  # The name of the dynamic field.
  title="Title",  # The name of the choice item.
  value="Some text ...",  # The value of the choice item.
                          # Hint: float | int | str
  is_delete=False, # True - if you need to remove the item of choice.
                   # by default = False (add item to choice)
)
await User.unit_manager(unit)

Instance methods

List of frequently used methods:

# Check data validity.
# The main use is to check data from web forms.
# It is also used to verify Models that do not migrate to the database.
user = User()
if not await user.is_valid():
    user.print_err()  # Convenient to use during development.

# Create or update document in database.
# This method pre-uses the `check` method.
user = User()
if not await user.save():
    user.print_err()  # Convenient to use during development.

# Delete document from database.
user = User()
await user.delete()
# or
await user.delete(remove_files=False)

# Verification, replacement and recoverang of password.
user = User()
await user.verify_password(password="12345678")
await user.update_password(  # uses verify_password
  old_password="12345678",
  new_password="O2eA4GIr38KGGlS",
)

Contributing

  1. Fork it (https://github.com/kebasyaty/ramifice/fork)
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

Install for development of Ramifice

# Fedora:
sudo dnf install gettext
gettext --version
# Ubuntu:
sudo apt install gettext
gettext --version
# Windows:
https://mlocati.github.io/articles/gettext-iconv-windows.html
gettext --version

cd project_name
uv sync

Contributors

  • kebasyaty Gennady Kostyunin - creator and maintainer

Changelog

View the change history.

License

This project is licensed under the MIT.

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.

ramifice-0.3.24-py3-none-any.whl (84.6 kB view details)

Uploaded Python 3

File details

Details for the file ramifice-0.3.24-py3-none-any.whl.

File metadata

  • Download URL: ramifice-0.3.24-py3-none-any.whl
  • Upload date:
  • Size: 84.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.7.13

File hashes

Hashes for ramifice-0.3.24-py3-none-any.whl
Algorithm Hash digest
SHA256 b517a3afbea67d3a09fa8ddf5c7fa500b59e109169bbf3eef80cc88ae77f5e94
MD5 fd7fba356a0e7dc96abb2de89ff7da27
BLAKE2b-256 b20b376aa56b4d5025f348457c9246343fad31a95de90c1e55a736866ff5138b

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