Name | ramifice JSON |
Version |
0.8.0
JSON |
| download |
home_page | None |
Summary | ORM-like API MongoDB for Python language. |
upload_time | 2025-07-09 07:27:53 |
maintainer | None |
docs_url | None |
author | None |
requires_python | <4.0,>=3.12 |
license | None |
keywords |
mongo
mongodb
orm
pymongo
ramifice
|
VCS |
 |
bugtrack_url |
|
requirements |
No requirements were recorded.
|
Travis-CI |
No Travis.
|
coveralls test coverage |
No coveralls.
|
<div align="center">
<p align="center">
<a href="https://github.com/kebasyaty/ramifice">
<img
height="90"
alt="Logo"
src="https://raw.githubusercontent.com/kebasyaty/ramifice/v0/assets/logo.svg">
</a>
</p>
<p>
<h1>ramifice</h1>
<h3>ORM-like API MongoDB for Python language.</h3>
<p align="center">
<a href="https://github.com/kebasyaty/ramifice/actions/workflows/test.yml" alt="Build Status"><img src="https://github.com/kebasyaty/ramifice/actions/workflows/test.yml/badge.svg" alt="Build Status"></a>
<a href="https://kebasyaty.github.io/ramifice/" alt="Docs"><img src="https://img.shields.io/badge/docs-available-brightgreen.svg" alt="Docs"></a>
<a href="https://pypi.python.org/pypi/ramifice/" alt="PyPI pyversions"><img src="https://img.shields.io/pypi/pyversions/ramifice.svg" alt="PyPI pyversions"></a>
<a href="https://pypi.python.org/pypi/ramifice/" alt="PyPI status"><img src="https://img.shields.io/pypi/status/ramifice.svg" alt="PyPI status"></a>
<a href="https://pypi.python.org/pypi/ramifice/" alt="PyPI version fury.io"><img src="https://badge.fury.io/py/ramifice.svg" alt="PyPI version fury.io"></a>
<br>
<a href="https://github.com/kebasyaty/ramifice/issues"><img src="https://img.shields.io/github/issues/kebasyaty/ramifice.svg" alt="GitHub issues"></a>
<a href="https://pepy.tech/projects/ramifice"><img src="https://static.pepy.tech/badge/ramifice" alt="PyPI Downloads"></a>
<a href="https://github.com/kebasyaty/ramifice/blob/main/LICENSE" alt="GitHub license"><img src="https://img.shields.io/github/license/kebasyaty/ramifice" alt="GitHub license"></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://github.com/kebasyaty/ramifice" alt="PyPI implementation"><img src="https://img.shields.io/pypi/implementation/ramifice" alt="PyPI implementation"></a>
<a href="https://github.com/kebasyaty/ramifice" alt="GitHub repository"><img src="https://img.shields.io/badge/--ecebeb?logo=github&logoColor=000000" alt="GitHub repository"></a>
</p>
<p align="center">
Ramifice is built around <a href="https://pypi.org/project/pymongo/" alt="PyMongo">PyMongo</a>.
<br>
For simulate relationship Many-to-One and Many-to-Many,
<br>
a simplified alternative (Types of selective fields with dynamic addition of elements) is used.
<br>
The project is more concentrated for web development or for applications with a graphic interface.
</p>
</p>
</div>
##
[](https://www.mongodb.com/)
<br>
_Supports MongoDB 3.6, 4.0, 4.2, 4.4, 5.0, 6.0, 7.0, and 8.0._
<br>
_For more information see [PyMongo](https://pypi.org/project/pymongo/ "PyMongo")_.
## Documentation
Online browsable documentation is available at [https://kebasyaty.github.io/ramifice/](https://kebasyaty.github.io/ramifice/ "Documentation").
## Requirements
[View the list of requirements.](https://github.com/kebasyaty/ramifice/blob/v0/REQUIREMENTS.md "View the list of requirements.")
## Installation
1. Install MongoDB (if not installed):<br>
[](https://github.com/kebasyaty/ramifice/blob/v0/assets/FEDORA_INSTALL_MONGODB.md)
[](https://github.com/kebasyaty/ramifice/blob/v0/assets/UBUNTU_INSTALL_MONGODB.md)
[](https://www.mongodb.com/try/download/community)
2. Run:
```shell
# 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
```
3. Add `config` and `public` directories in root of your project:<br>
[Download config directory](https://downgit.github.io/#/home?url=https://github.com/kebasyaty/ramifice/tree/main/config "Download config directory")
<br>
[Download public directory](https://downgit.github.io/#/home?url=https://github.com/kebasyaty/ramifice/tree/main/public "Download public directory")
## Usage
It is recommended to look at examples [here](https://github.com/kebasyaty/ramifice/tree/v0/examples "here").
```python
import re
import asyncio
from datetime import datetime
import pprint
from pymongo import AsyncMongoClient
from ramifice import model, translations, Migration
from ramifice.fields import (
ImageField,
PasswordField,
TextField,
)
from ramifice.utils.tools import to_human_size
@model(service_name="Accounts")
class User:
"""Model of User."""
def fields(self) -> None:
"""For adding fields."""
# For custom translations.
gettext = translations.gettext
# ngettext = translations.ngettext
self.avatar = ImageField(
label=gettext("Avatar"),
default="public/media/default/no-photo.png",
# Directory for images inside media directory.
target_dir="users/avatars",
# Available 4 sizes from lg to xs or None.
# Hint: By default = None
thumbnails={"lg": 512, "md": 256, "sm": 128, "xs": 64},
# The maximum size of the original image in bytes.
# Hint: By default = 2 MB
max_size=524288, # 0.5 MB = 512 KB = 524288 Bytes (in binary)
warning=[
gettext("Maximum size: %s") % to_human_size(524288),
],
)
self.username = TextField(
label=gettext("Username"),
maxlength=150,
required=True,
unique=True,
warning=[
gettext("Allowed chars: %s") % "a-z A-Z 0-9 _",
],
)
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,
)
# Optional method.
async def add_validation(self) -> dict[str, str]:
"""Additional validation of fields."""
gettext = translations.gettext
error_map: dict[str, str] = {}
# Get clean data.
id = self.id.value
username = self.username.value
password = self.password.value
сonfirm_password = self.сonfirm_password.value
if re.match(r"^[a-zA-Z0-9_]+$", username) is None:
error_map["username"] = gettext("Allowed chars: %s") % "a-z A-Z 0-9 _"
if id is None and (password != сonfirm_password):
error_map["password"] = gettext("Passwords do not match!")
return error_map
async def main():
client = AsyncMongoClient()
await Migration(
database_name="test_db",
mongo_client=client,
).migrate()
# If you need to change the language of translation.
# Hint: For Ramifice by default = "en"
translations.change_locale("en")
user = User()
# user.avatar.from_path("public/media/default/no-photo.png")
user.username.value = "pythondev"
user.password.value = "12345678"
user.сonfirm_password.value = "12345678"
# Create User.
if not await user.save():
# Convenient to use during development.
user.print_err()
# Update User.
user.username.value = "pythondev_123"
if not await user.save():
user.print_err()
print("User details:")
user_details = await User.find_one_to_raw_doc(
# {"_id": user.id.value}
{f"username": user.username.value}
)
if user_details is not None:
pprint.pprint(user_details)
else:
print("No User!")
# Remove User.
# (if necessary)
# await user.delete()
# await user.delete(remove_files=False)
# Remove collection.
# (if necessary)
# await User.collection().drop()
if __name__ == "__main__":
asyncio.run(main())
```
### How to create custom translations ?
```python
from ramifice import translations
translations.DEFAULT_LOCALE = "en" # For Ramifice by default = "en"
LANGUAGES = frozenset(("en", "ru")) # For Ramifice by default = ["en", "ru"]
```
```shell
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 ?
```python
from ramifice import translations
translations.DEFAULT_LOCALE = "en" # For Ramifice by default = "en"
translations.LANGUAGES = frozenset(("en", "ru", "de", "de_ch")) # For Ramifice by default = ["en", "ru"]
```
```shell
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
```
## Model Parameters
See the documentation [here](https://kebasyaty.github.io/ramifice/ "here").
###### ( only `service_name` is a required parameter )
<div>
<table>
<tr>
<th align="left">Parameter</th>
<th align="left">Default</th>
<th align="left">Description</th>
</tr>
<tr>
<td align="left">service_name</td>
<td align="left">no</td>
<td align="left"><b>Examples:</b> Accounts | Smartphones | Washing machines | etc ... </td>
</tr>
<tr>
<td align="left">fixture_name</td>
<td align="left">None</td>
<td align="left">
The name of the fixture in the <b>config/fixtures</b> directory (without extension).
<br>
<b>Examples:</b> SiteSettings | AppSettings | etc ...
</td>
</tr>
<tr>
<td align="left">db_query_docs_limit</td>
<td align="left">1000</td>
<td align="left">limiting query results.</td>
</tr>
<tr>
<td align="left">is_create_doc</td>
<td align="left">True</td>
<td align="left">
Can a Model create new documents in a collection?<br>
Set to <b>False</b> if you only need one document in the collection and the Model is using a fixture.
</td>
</tr>
<tr>
<td align="left">is_update_doc</td>
<td align="left">True</td>
<td align="left">Can a Model update documents in a collection?</td>
</tr>
<tr>
<td align="left">is_delete_doc</td>
<td align="left">True</td>
<td align="left">Can a Model remove documents from a collection?</td>
</tr>
</table>
</div>
<br>
**Example:**
```python
@model(
service_name="ServiceName",
fixture_name="FixtureName",
db_query_docs_limit=1000,
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:_
```python
# 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)
# Create object instance from Mongo document.
q_filter = {"email": "John_Smith@gmail.com"}
mongo_doc = await User.find_one(q_filter)
user = User.from_mongo_doc(mongo_doc)
# 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 import Unit
unit = Unit(
field="field_name", # The name of the dynamic field.
title={"en": "Title", "ru": "Заголовок"}, # 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:_
```python
# 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( # + verify_password
old_password="12345678",
new_password="O2eA4GIr38KGGlS",
)
```
## General auxiliary methods
```python
from ramifice.utils.tools import (
get_file_size,
hash_to_obj_id,
is_color,
is_email,
is_ip,
is_mongo_id,
is_password,
is_phone,
is_url,
normal_email,
to_human_size,
)
# Validate Password.
if is_password("12345678"):
...
# Validate Email address.
if await is_email("kebasyaty@gmail.com"):
...
# Normalizing email address.
# Use this before requeste to a database.
# For example, on the login page.
email: str | None = normal_email("kebasyaty@gmail.com") # None, if not valid
# Validate URL address.
if is_url("https://www.google.com"):
...
# Validate IP address.
if is_ip("127.0.0.1"):
...
# Validate Color code.
if is_color("#000"):
...
# Validate Phone number.
if is_phone("+447986123456"):
...
# Validation of the Mongodb identifier.
if is_mongo_id("666f6f2d6261722d71757578"):
...
# Get ObjectId from hash string.
from bson.objectid import ObjectId
_id: ObjectId | None = hash_to_obj_id("666f6f2d6261722d71757578")
# Convert number of bytes to readable format.
size: str = to_human_size(2097152) # => 2.0 MB
# Get file size in bytes.
path = "public/media/default/no_doc.odt"
size: int = get_file_size(path) # => 9843
```
## 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
```shell
# 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](https://github.com/kebasyaty) Gennady Kostyunin - creator and maintainer
## Changelog
[View the change history.](https://github.com/kebasyaty/ramifice/blob/v0/CHANGELOG.md "Changelog")
## License
**This project is licensed under the** [MIT](https://github.com/kebasyaty/ramifice/blob/main/LICENSE "MIT")**.**
Raw data
{
"_id": null,
"home_page": null,
"name": "ramifice",
"maintainer": null,
"docs_url": null,
"requires_python": "<4.0,>=3.12",
"maintainer_email": null,
"keywords": "mongo, mongodb, orm, pymongo, ramifice",
"author": null,
"author_email": "Gennady Kostyunin <kebasyaty@gmail.com>",
"download_url": null,
"platform": null,
"description": "<div align=\"center\">\n <p align=\"center\">\n <a href=\"https://github.com/kebasyaty/ramifice\">\n <img\n height=\"90\"\n alt=\"Logo\"\n src=\"https://raw.githubusercontent.com/kebasyaty/ramifice/v0/assets/logo.svg\">\n </a>\n </p>\n <p>\n <h1>ramifice</h1>\n <h3>ORM-like API MongoDB for Python language.</h3>\n <p align=\"center\">\n <a href=\"https://github.com/kebasyaty/ramifice/actions/workflows/test.yml\" alt=\"Build Status\"><img src=\"https://github.com/kebasyaty/ramifice/actions/workflows/test.yml/badge.svg\" alt=\"Build Status\"></a>\n <a href=\"https://kebasyaty.github.io/ramifice/\" alt=\"Docs\"><img src=\"https://img.shields.io/badge/docs-available-brightgreen.svg\" alt=\"Docs\"></a>\n <a href=\"https://pypi.python.org/pypi/ramifice/\" alt=\"PyPI pyversions\"><img src=\"https://img.shields.io/pypi/pyversions/ramifice.svg\" alt=\"PyPI pyversions\"></a>\n <a href=\"https://pypi.python.org/pypi/ramifice/\" alt=\"PyPI status\"><img src=\"https://img.shields.io/pypi/status/ramifice.svg\" alt=\"PyPI status\"></a>\n <a href=\"https://pypi.python.org/pypi/ramifice/\" alt=\"PyPI version fury.io\"><img src=\"https://badge.fury.io/py/ramifice.svg\" alt=\"PyPI version fury.io\"></a>\n <br>\n <a href=\"https://github.com/kebasyaty/ramifice/issues\"><img src=\"https://img.shields.io/github/issues/kebasyaty/ramifice.svg\" alt=\"GitHub issues\"></a>\n <a href=\"https://pepy.tech/projects/ramifice\"><img src=\"https://static.pepy.tech/badge/ramifice\" alt=\"PyPI Downloads\"></a>\n <a href=\"https://github.com/kebasyaty/ramifice/blob/main/LICENSE\" alt=\"GitHub license\"><img src=\"https://img.shields.io/github/license/kebasyaty/ramifice\" alt=\"GitHub license\"></a>\n <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>\n <a href=\"https://github.com/kebasyaty/ramifice\" alt=\"PyPI implementation\"><img src=\"https://img.shields.io/pypi/implementation/ramifice\" alt=\"PyPI implementation\"></a>\n <a href=\"https://github.com/kebasyaty/ramifice\" alt=\"GitHub repository\"><img src=\"https://img.shields.io/badge/--ecebeb?logo=github&logoColor=000000\" alt=\"GitHub repository\"></a>\n </p>\n <p align=\"center\">\n Ramifice is built around <a href=\"https://pypi.org/project/pymongo/\" alt=\"PyMongo\">PyMongo</a>.\n <br>\n For simulate relationship Many-to-One and Many-to-Many,\n <br>\n a simplified alternative (Types of selective fields with dynamic addition of elements) is used.\n <br>\n The project is more concentrated for web development or for applications with a graphic interface.\n </p>\n </p>\n</div>\n\n##\n\n[](https://www.mongodb.com/)\n<br>\n_Supports MongoDB 3.6, 4.0, 4.2, 4.4, 5.0, 6.0, 7.0, and 8.0._\n<br>\n_For more information see [PyMongo](https://pypi.org/project/pymongo/ \"PyMongo\")_.\n\n## Documentation\n\nOnline browsable documentation is available at [https://kebasyaty.github.io/ramifice/](https://kebasyaty.github.io/ramifice/ \"Documentation\").\n\n## Requirements\n\n[View the list of requirements.](https://github.com/kebasyaty/ramifice/blob/v0/REQUIREMENTS.md \"View the list of requirements.\")\n\n## Installation\n\n1. Install MongoDB (if not installed):<br>\n [](https://github.com/kebasyaty/ramifice/blob/v0/assets/FEDORA_INSTALL_MONGODB.md)\n [](https://github.com/kebasyaty/ramifice/blob/v0/assets/UBUNTU_INSTALL_MONGODB.md)\n [](https://www.mongodb.com/try/download/community)\n\n2. Run:\n\n```shell\n# Fedora:\nsudo dnf install gettext\ngettext --version\n# Ubuntu:\nsudo apt install gettext\ngettext --version\n# Windows:\nhttps://mlocati.github.io/articles/gettext-iconv-windows.html\ngettext --version\n\ncd project_name\nuv add ramifice\n```\n\n3. Add `config` and `public` directories in root of your project:<br>\n [Download config directory](https://downgit.github.io/#/home?url=https://github.com/kebasyaty/ramifice/tree/main/config \"Download config directory\")\n <br>\n [Download public directory](https://downgit.github.io/#/home?url=https://github.com/kebasyaty/ramifice/tree/main/public \"Download public directory\")\n\n## Usage\n\nIt is recommended to look at examples [here](https://github.com/kebasyaty/ramifice/tree/v0/examples \"here\").\n\n```python\nimport re\nimport asyncio\nfrom datetime import datetime\nimport pprint\n\nfrom pymongo import AsyncMongoClient\nfrom ramifice import model, translations, Migration\nfrom ramifice.fields import (\n ImageField,\n PasswordField,\n TextField,\n)\nfrom ramifice.utils.tools import to_human_size\n\n\n@model(service_name=\"Accounts\")\nclass User:\n \"\"\"Model of User.\"\"\"\n\n def fields(self) -> None:\n \"\"\"For adding fields.\"\"\"\n # For custom translations.\n gettext = translations.gettext\n # ngettext = translations.ngettext\n self.avatar = ImageField(\n label=gettext(\"Avatar\"),\n default=\"public/media/default/no-photo.png\",\n # Directory for images inside media directory.\n target_dir=\"users/avatars\",\n # Available 4 sizes from lg to xs or None.\n # Hint: By default = None\n thumbnails={\"lg\": 512, \"md\": 256, \"sm\": 128, \"xs\": 64},\n # The maximum size of the original image in bytes.\n # Hint: By default = 2 MB\n max_size=524288, # 0.5 MB = 512 KB = 524288 Bytes (in binary)\n warning=[\n gettext(\"Maximum size: %s\") % to_human_size(524288),\n ],\n )\n self.username = TextField(\n label=gettext(\"Username\"),\n maxlength=150,\n required=True,\n unique=True,\n warning=[\n gettext(\"Allowed chars: %s\") % \"a-z A-Z 0-9 _\",\n ],\n )\n self.password = PasswordField(\n label=gettext(\"Password\"),\n )\n self.\u0441onfirm_password = PasswordField(\n label=gettext(\"Confirm password\"),\n # If true, the value of this field is not saved in the database.\n ignored=True,\n )\n\n\n # Optional method.\n async def add_validation(self) -> dict[str, str]:\n \"\"\"Additional validation of fields.\"\"\"\n gettext = translations.gettext\n error_map: dict[str, str] = {}\n\n # Get clean data.\n id = self.id.value\n username = self.username.value\n password = self.password.value\n \u0441onfirm_password = self.\u0441onfirm_password.value\n\n if re.match(r\"^[a-zA-Z0-9_]+$\", username) is None:\n error_map[\"username\"] = gettext(\"Allowed chars: %s\") % \"a-z A-Z 0-9 _\"\n\n if id is None and (password != \u0441onfirm_password):\n error_map[\"password\"] = gettext(\"Passwords do not match!\")\n return error_map\n\n\nasync def main():\n client = AsyncMongoClient()\n\n await Migration(\n database_name=\"test_db\",\n mongo_client=client,\n ).migrate()\n\n # If you need to change the language of translation.\n # Hint: For Ramifice by default = \"en\"\n translations.change_locale(\"en\")\n\n user = User()\n # user.avatar.from_path(\"public/media/default/no-photo.png\")\n user.username.value = \"pythondev\"\n user.password.value = \"12345678\"\n user.\u0441onfirm_password.value = \"12345678\"\n\n # Create User.\n if not await user.save():\n # Convenient to use during development.\n user.print_err()\n\n # Update User.\n user.username.value = \"pythondev_123\"\n if not await user.save():\n user.print_err()\n\n print(\"User details:\")\n user_details = await User.find_one_to_raw_doc(\n # {\"_id\": user.id.value}\n {f\"username\": user.username.value}\n )\n if user_details is not None:\n pprint.pprint(user_details)\n else:\n print(\"No User!\")\n\n # Remove User.\n # (if necessary)\n # await user.delete()\n # await user.delete(remove_files=False)\n\n # Remove collection.\n # (if necessary)\n # await User.collection().drop()\n\n\nif __name__ == \"__main__\":\n asyncio.run(main())\n```\n\n### How to create custom translations ?\n\n```python\nfrom ramifice import translations\n\ntranslations.DEFAULT_LOCALE = \"en\" # For Ramifice by default = \"en\"\nLANGUAGES = frozenset((\"en\", \"ru\")) # For Ramifice by default = [\"en\", \"ru\"]\n```\n\n```shell\ncd project_name\n# Add your custom translations:\nuv run pybabel extract -o config/translations/custom.pot src\nuv run pybabel init -i config/translations/custom.pot -d config/translations/custom -l en\nuv run pybabel init -i config/translations/custom.pot -d config/translations/custom -l ru\n...\n# Hint: Do not forget to add translations for new languages.\nuv run pybabel compile -d config/translations/custom\n\n# Update your custom translations:\nuv run pybabel extract -o config/translations/custom.pot src\nuv run pybabel update -i config/translations/custom.pot -d config/translations/custom\n# Hint: Do not forget to check the translations for existing languages.\nuv run pybabel compile -d config/translations/custom\n```\n\n### How to add new languages \u200b\u200bto Ramifice ?\n\n```python\nfrom ramifice import translations\n\ntranslations.DEFAULT_LOCALE = \"en\" # For Ramifice by default = \"en\"\ntranslations.LANGUAGES = frozenset((\"en\", \"ru\", \"de\", \"de_ch\")) # For Ramifice by default = [\"en\", \"ru\"]\n```\n\n```shell\ncd project_name\n# Example:\nuv run pybabel init -i config/translations/ramifice.pot -d config/translations/ramifice -l de\nuv run pybabel init -i config/translations/ramifice.pot -d config/translations/ramifice -l de_ch\n...\n# Hint: Do not forget to add translations for new languages.\nuv run pybabel compile -d config/translations/ramifice\n\n# Update translations to Ramifice:\nuv run pybabel extract -o config/translations/ramifice.pot ramifice\nuv run pybabel update -i config/translations/ramifice.pot -d config/translations/ramifice\n# Hint: Do not forget to check the translations for existing languages.\nuv run pybabel compile -d config/translations/ramifice\n```\n\n## Model Parameters\n\nSee the documentation [here](https://kebasyaty.github.io/ramifice/ \"here\").\n\n###### ( only `service_name` is a required parameter )\n\n<div>\n <table>\n <tr>\n <th align=\"left\">Parameter</th>\n <th align=\"left\">Default</th>\n <th align=\"left\">Description</th>\n </tr>\n <tr>\n <td align=\"left\">service_name</td>\n <td align=\"left\">no</td>\n <td align=\"left\"><b>Examples:</b> Accounts | Smartphones | Washing machines | etc ... </td>\n </tr>\n <tr>\n <td align=\"left\">fixture_name</td>\n <td align=\"left\">None</td>\n <td align=\"left\">\n The name of the fixture in the <b>config/fixtures</b> directory (without extension).\n <br>\n <b>Examples:</b> SiteSettings | AppSettings | etc ...\n </td>\n </tr>\n <tr>\n <td align=\"left\">db_query_docs_limit</td>\n <td align=\"left\">1000</td>\n <td align=\"left\">limiting query results.</td>\n </tr>\n <tr>\n <td align=\"left\">is_create_doc</td>\n <td align=\"left\">True</td>\n <td align=\"left\">\n Can a Model create new documents in a collection?<br>\n Set to <b>False</b> if you only need one document in the collection and the Model is using a fixture.\n </td>\n </tr>\n <tr>\n <td align=\"left\">is_update_doc</td>\n <td align=\"left\">True</td>\n <td align=\"left\">Can a Model update documents in a collection?</td>\n </tr>\n <tr>\n <td align=\"left\">is_delete_doc</td>\n <td align=\"left\">True</td>\n <td align=\"left\">Can a Model remove documents from a collection?</td>\n </tr>\n </table>\n</div>\n\n<br>\n\n**Example:**\n\n```python\n@model(\n service_name=\"ServiceName\",\n fixture_name=\"FixtureName\",\n db_query_docs_limit=1000,\n is_create_doc = True,\n is_update_doc = True,\n is_delete_doc = True,\n)\nclass User:\n def fields(self):\n self.username = TextField(\n label=gettext(\"Username\"),\n required=True,\n unique=True,\n )\n```\n\n## Class methods\n\n_List of frequently used methods:_\n\n```python\n# Gets an estimate of the count of documents in a collection using collection metadata.\ncount: int = await User.estimated_document_count()\n\n# Gets an estimate of the count of documents in a collection using collection metadata.\nq_filter = {\"first_name\": \"John\"}\ncount: int = await User.count_documents(q_filter)\n\n# Runs an aggregation framework pipeline.\nfrom bson.bson import BSON\npipeline = [\n {\"$unwind\": \"$tags\"},\n {\"$group\": {\"_id\": \"$tags\", \"count\": {\"$sum\": 1}}},\n {\"$sort\": BSON([(\"count\", -1), (\"_id\", -1)])},\n]\ndocs = await User.aggregate(pipeline)\n\n# Finds the distinct values for a specified field across a single collection.\nq_filter = \"key_name\"\nvalues = await User.distinct(q_filter)\n\n# Get collection name.\nname = await User.collection_name()\n\n# The full name is of the form database_name.collection_name.\nname = await User.collection_full_name()\n\n# Get AsyncBatabase for the current Model.\ndatabase = await User.database()\n\n# Get AsyncCollection for the current Model.\ncollection = await User.collection()\n\n# Find a single document.\nq_filter = {\"email\": \"John_Smith@gmail.com\"}\nmongo_doc = await User.find_one(q_filter)\n\n# Create object instance from Mongo document.\nq_filter = {\"email\": \"John_Smith@gmail.com\"}\nmongo_doc = await User.find_one(q_filter)\nuser = User.from_mongo_doc(mongo_doc)\n\n# Find a single document and converting to raw document.\nq_filter = {\"email\": \"John_Smith@gmail.com\"}\nraw_doc = await User.find_one_to_raw_doc(q_filter)\n\n# Find a single document and convert it to a Model instance.\nq_filter = {\"email\": \"John_Smith@gmail.com\"}\nuser = await User.find_one_to_instance(q_filter)\n\n# Find a single document and convert it to a JSON string.\nq_filter = {\"email\": \"John_Smith@gmail.com\"}\njson = await User.find_one_to_json(q_filter)\n\n# Find a single document and delete it.\nq_filter = {\"email\": \"John_Smith@gmail.com\"}\ndelete_result = await User.delete_one(q_filter)\n\n# Find a single document and delete it, return original.\nq_filter = {\"email\": \"John_Smith@gmail.com\"}\nmongo_doc = await User.find_one_and_delete(q_filter)\n\n# Find documents.\nq_filter = {\"first_name\": \"John\"}\nmongo_docs = await User.find_many(q_filter)\n\n# Find documents and convert to a raw documents.\nq_filter = {\"first_name\": \"John\"}\nraw_docs = await User.find_many_to_raw_docs(q_filter)\n\n# Find documents and convert to a json string.\nq_filter = {\"email\": \"John_Smith@gmail.com\"}\njson = await User.find_many_to_json(q_filter)\n\n# Find documents matching with Model.\nq_filter = {\"email\": \"John_Smith@gmail.com\"}\ndelete_result = await User.delete_many(q_filter)\n\n# Creates an index on this collection.\nfrom pymongo import ASCENDING\nkeys = [(\"email\", ASCENDING)]\nresult: str = await User.create_index(keys, name=\"idx_email\")\n\n# Drops the specified index on this collection.\nUser.drop_index(\"idx_email\")\n\n# Create one or more indexes on this collection.\nfrom pymongo import ASCENDING, DESCENDING\nindex_1 = IndexModel([(\"username\", DESCENDING), (\"email\", ASCENDING)], name=\"idx_username_email\")\nindex_2 = IndexModel([(\"first_name\", DESCENDING)], name=\"idx_first_name\")\nresult: list[str] = await User.create_indexes([index_1, index_2])\n\n# Drops all indexes on this collection.\nUser.drop_index()\n\n# Get information on this collection\u2019s indexes.\nresult = await User.index_information()\n\n# Get a cursor over the index documents for this collection.\nasync for index in await User.list_indexes():\n print(index)\n\n# Units Management.\n# Management for `choices` parameter in dynamic field types.\n# Units are stored in a separate collection.\nfrom ramifice import Unit\nunit = Unit(\n field=\"field_name\", # The name of the dynamic field.\n title={\"en\": \"Title\", \"ru\": \"\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a\"}, # The name of the choice item.\n value=\"Some text ...\", # The value of the choice item.\n # Hint: float | int | str\n is_delete=False, # True - if you need to remove the item of choice.\n # by default = False (add item to choice)\n)\nawait User.unit_manager(unit)\n```\n\n## Instance methods\n\n_List of frequently used methods:_\n\n```python\n# Check data validity.\n# The main use is to check data from web forms.\n# It is also used to verify Models that do not migrate to the database.\nuser = User()\nif not await user.is_valid():\n user.print_err() # Convenient to use during development.\n\n# Create or update document in database.\n# This method pre-uses the `check` method.\nuser = User()\nif not await user.save():\n user.print_err() # Convenient to use during development.\n\n# Delete document from database.\nuser = User()\nawait user.delete()\n# or\nawait user.delete(remove_files=False)\n\n# Verification, replacement and recoverang of password.\nuser = User()\nawait user.verify_password(password=\"12345678\")\nawait user.update_password( # + verify_password\n old_password=\"12345678\",\n new_password=\"O2eA4GIr38KGGlS\",\n)\n```\n\n## General auxiliary methods\n\n```python\nfrom ramifice.utils.tools import (\n get_file_size,\n hash_to_obj_id,\n is_color,\n is_email,\n is_ip,\n is_mongo_id,\n is_password,\n is_phone,\n is_url,\n normal_email,\n to_human_size,\n)\n\n# Validate Password.\nif is_password(\"12345678\"):\n ...\n\n# Validate Email address.\nif await is_email(\"kebasyaty@gmail.com\"):\n ...\n\n# Normalizing email address.\n# Use this before requeste to a database.\n# For example, on the login page.\nemail: str | None = normal_email(\"kebasyaty@gmail.com\") # None, if not valid\n\n# Validate URL address.\nif is_url(\"https://www.google.com\"):\n ...\n\n# Validate IP address.\nif is_ip(\"127.0.0.1\"):\n ...\n\n# Validate Color code.\nif is_color(\"#000\"):\n ...\n\n# Validate Phone number.\nif is_phone(\"+447986123456\"):\n ...\n\n# Validation of the Mongodb identifier.\nif is_mongo_id(\"666f6f2d6261722d71757578\"):\n ...\n\n# Get ObjectId from hash string.\nfrom bson.objectid import ObjectId\n_id: ObjectId | None = hash_to_obj_id(\"666f6f2d6261722d71757578\")\n\n# Convert number of bytes to readable format.\nsize: str = to_human_size(2097152) # => 2.0 MB\n\n# Get file size in bytes.\npath = \"public/media/default/no_doc.odt\"\nsize: int = get_file_size(path) # => 9843\n```\n\n## Contributing\n\n1. Fork it (<https://github.com/kebasyaty/ramifice/fork>)\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Commit your changes (`git commit -am 'Add some feature'`)\n4. Push to the branch (`git push origin my-new-feature`)\n5. Create a new Pull Request\n\n## Install for development of Ramifice\n\n```shell\n# Fedora:\nsudo dnf install gettext\ngettext --version\n# Ubuntu:\nsudo apt install gettext\ngettext --version\n# Windows:\nhttps://mlocati.github.io/articles/gettext-iconv-windows.html\ngettext --version\n\ncd project_name\nuv sync\n```\n\n## Contributors\n\n- [kebasyaty](https://github.com/kebasyaty) Gennady Kostyunin - creator and maintainer\n\n## Changelog\n\n[View the change history.](https://github.com/kebasyaty/ramifice/blob/v0/CHANGELOG.md \"Changelog\")\n\n## License\n\n**This project is licensed under the** [MIT](https://github.com/kebasyaty/ramifice/blob/main/LICENSE \"MIT\")**.**\n",
"bugtrack_url": null,
"license": null,
"summary": "ORM-like API MongoDB for Python language.",
"version": "0.8.0",
"project_urls": {
"Bug Tracker": "https://github.com/kebasyaty/ramifice/issues",
"Changelog": "https://github.com/kebasyaty/ramifice/blob/v0/CHANGELOG.md",
"Documentation": "https://kebasyaty.github.io/ramifice/",
"Homepage": "https://github.com/kebasyaty/ramifice",
"Repository": "https://github.com/kebasyaty/ramifice",
"Source": "https://github.com/kebasyaty/ramifice"
},
"split_keywords": [
"mongo",
" mongodb",
" orm",
" pymongo",
" ramifice"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "1bc3bf3d097216637aaedbc113ebe1a644202d15cb154029050589522b305dab",
"md5": "23585d3224df6663a099e39b8872caf7",
"sha256": "bee45f7ac8908d4f7bc4bdbd5121e18085d4e1e1f76af1d9f6003cd344e8ee95"
},
"downloads": -1,
"filename": "ramifice-0.8.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "23585d3224df6663a099e39b8872caf7",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": "<4.0,>=3.12",
"size": 87052,
"upload_time": "2025-07-09T07:27:53",
"upload_time_iso_8601": "2025-07-09T07:27:53.731964Z",
"url": "https://files.pythonhosted.org/packages/1b/c3/bf3d097216637aaedbc113ebe1a644202d15cb154029050589522b305dab/ramifice-0.8.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-07-09 07:27:53",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "kebasyaty",
"github_project": "ramifice",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "ramifice"
}