{"id":27042326,"url":"https://github.com/rickh94/odetam","last_synced_at":"2025-04-05T04:28:20.329Z","repository":{"id":57447821,"uuid":"318719447","full_name":"rickh94/ODetaM","owner":"rickh94","description":"A simple ODM(Object Document Mapper) for Deta Base based on pydantic","archived":false,"fork":false,"pushed_at":"2023-10-10T18:03:12.000Z","size":148,"stargazers_count":36,"open_issues_count":3,"forks_count":2,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-03-16T21:49:11.789Z","etag":null,"topics":["database-management","deta","odm","pydantic","python"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rickh94.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2020-12-05T06:32:00.000Z","updated_at":"2025-01-24T13:04:54.000Z","dependencies_parsed_at":"2024-01-06T01:03:58.934Z","dependency_job_id":"5bca3617-0f0c-49a1-8533-a9fc37bf90af","html_url":"https://github.com/rickh94/ODetaM","commit_stats":{"total_commits":49,"total_committers":3,"mean_commits":"16.333333333333332","dds":"0.10204081632653061","last_synced_commit":"f1c705dab3185441d296ff42d0ba7e95f68e011d"},"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rickh94%2FODetaM","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rickh94%2FODetaM/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rickh94%2FODetaM/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rickh94%2FODetaM/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rickh94","download_url":"https://codeload.github.com/rickh94/ODetaM/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247289445,"owners_count":20914463,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["database-management","deta","odm","pydantic","python"],"created_at":"2025-04-05T04:28:19.835Z","updated_at":"2025-04-05T04:28:20.314Z","avatar_url":"https://github.com/rickh94.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ODetaM\n\n[![Test](https://github.com/rickh94/ODetaM/actions/workflows/test.yml/badge.svg)](https://github.com/rickh94/ODetaM/actions/workflows/test.yml)\n[![codecov](https://codecov.io/gh/rickh94/odetam/branch/main/graph/badge.svg?token=BLDIMHU9FB)](https://codecov.io/gh/rickh94/odetam)\n\nA simple ODM (Object Document Mapper) for [Deta Base](https://deta.sh) base on\n[pydantic](https://github.com/samuelcolvin/pydantic/).\n\n## Installation\n\n`pip install odetam`\n\n## Usage\n\nCreate pydantic models as normal, but inherit from `DetaModel` instead of \npydantic BaseModel. You will need to set the environment variable \n`DETA_PROJECT_KEY` to your Deta project key so that databases can be \naccessed/created, instead you are working under deta initialized project. \nYour also can specify Deta project key in Config class of your model, for \nmigration from Deta Cloud or importing external Collection (read [DetaBase Docs](https://deta.space/docs/en/basics/data)) \nThis is a secret key, so handle it appropriately (hence the environment variable).\n\nBases will be automatically created based on model names (changed from \nPascalCase/CamelCase case to snake_case). A `key` field (Deta's unique id) will \nbe automatically added to any model. You can supply the key on creation, or \nDeta will generate one automatically and it will be added to the object when it \nis saved.\n\n## Async Support\n\nAsync/await is now supported! As of version 1.2.0, you can now \n`from odetam.async_model import AsyncDetaModel`, inherit from that, and run all \nthe examples below just the same, but with `await` in front of the calls.\n\nYou must `pip install deta[async]`, to use asynchronous base.\n\n\n### Get All\n\n`DetaModel.get_all()` should handle large bases better now, but you should \nconsider querying instead of getting everything if possible, because it is\nunlikely to perform well on large bases.\n\n\n## Example\n\n### Basics\n\n```python\nimport datetime\nfrom typing import List\n\nfrom odetam import DetaModel\n\n\nclass Captain(DetaModel):\n    name: str\n    joined: datetime.date\n    ships: List[str]\n\n\n# create\nkirk = Captain(\n        name=\"James T. Kirk\",\n        joined=datetime.date(2252, 1, 1),\n        ships=[\"Enterprise\"],\n        )\n\nsisko = Captain(\n        name=\"Benjamin Sisko\",\n        joined=datetime.date(2350, 1, 1),\n        ships=[\"Deep Space 9\", \"Defiant\"],\n        )\n\n# initial save, key is now set\nkirk.save()\n\n# update the object\nkirk.ships.append(\"Enterprise-A\")\n\n# save again, this will be an update\nkirk.save()\n\nsisko.save()\n\nCaptain.get_all()\n# [\n#     Captain(\n#         name=\"James T. Kirk\", \n#         joined=datetime.date(2252, 01, 01), \n#         ships=[\"Enterprise\", \"Enterprise-A\"],\n#         key=\"key1\",\n#     ),\n#     Captain(\n#         name=\"Benjamin Sisko\",\n#         joined=datetime.date(2350, 01, 01), \n#         ships=[\"Deep Space 9\", \"Defiant\"],\n#         key=\"key2\",\n#     ),\n# ]\n\nCaptain.get(\"key1\")\n# Captain(\n#     name=\"James T. Kirk\", \n#     joined=datetime.date(2252, 01, 01), \n#     ships=[\"Enterprise\", \"Enterprise-A\"],\n#     key=\"key1\",\n# )\n\nCaptain.get(\"key3\")\n# Traceback (most recent call last):\n#   File \"\u003cstdin\u003e\", line 1, in \u003cmodule\u003e\n# odetam.exceptions.ItemNotFound\n\nCaptain.get_or_none(\"key3\")\n# None\n\nCaptain.query(Captain.name == \"James T. Kirk\")\n# Captain(\n#     name=\"James T. Kirk\", \n#     joined=datetime.date(2252, 01, 01), \n#     ships=[\"Enterprise\", \"Enterprise-A\"],\n#     key=\"key1\",\n# )\n\nCaptain.query(Captain.ships.contains(\"Defiant\"))\n# Captain(\n#     name=\"Benjamin Sisko\",\n#     joined=datetime.date(2350, 01, 01),\n#     ships=[\"Deep Space 9\", \"Defiant\"],\n# )\n\nCaptain.query(Captain.name.prefix(\"Ben\"))\n# Captain(\n#     name=\"Benjamin Sisko\",\n#     joined=datetime.date(2350, 01, 01),\n#     ships=[\"Deep Space 9\", \"Defiant\"],\n# )\n\nkirk.delete()\nCaptain.delete_key(\"key2\")\n\nCaptain.get_all()\n# []\n\n# you can also save several at once for better speed\nCaptain.put_many([kirk, sisko])\n# [\n#     Captain(\n#         name=\"James T. Kirk\", \n#         joined=datetime.date(2252, 01, 01), \n#         ships=[\"Enterprise\", \"Enterprise-A\"],\n#         key=\"key1\",\n#     ),\n#     Captain(\n#         name=\"Benjamin Sisko\",\n#         joined=datetime.date(2350, 01, 01), \n#         ships=[\"Deep Space 9\", \"Defiant\"],\n#         key=\"key2\",\n#     ),\n# ]\n\n```\n\n### Async model\n\n```python\nimport datetime\nfrom typing import List\n\nfrom odetam.async_model import AsyncDetaModel\n\n\nclass Captain(AsyncDetaModel):\n    name: str\n    joined: datetime.date\n    ships: List[str]\n\n\nasync foo():\n    items = await Captain.get_all()\n\n```\n\n### Config\n\n```python\n\nclass Captain(AsyncDetaModel):\n    name: str\n    joined: datetime.date\n    ships: List[str]\n\n    class Config:\n        table_name = \"my_custom_table_name\"\n        deta_key = \"123_123\" # project key from Deta Cloud or Data Key from another Deta Space project\n\n```\n\n## Save\n\nModels have the `.save()` method which will always behave as an upsert, \nupdating a record if it has a key, otherwise creating it and setting a key. \nDeta has pure insert behavior, but it's less performant. If you need it, please \nopen a pull request.\n\n## Querying\n\nAll basic comparison operators are implemented to map to their equivalents as \n`(Model.field \u003e= comparison_value)`. There is also a `.contains()` and \n`.not_contains()` method for strings and lists of strings, as well as a \n`.prefix()` method for strings. There is also a `.range()` for number types \nthat takes a lower and upper bound. You can also use `\u0026`  as AND and `|` as OR. \nORs cannot be nested within ands, use a list of options as comparison instead. \nYou can use as many ORs as you want, as long as they execute after the ANDs in \nthe order of operations. This is due to how the Deta Base api works.\n\n## Deta Base\n\nDirect access to the base is available in the dunder attribute `__db__`, though \nthe point is to avoid that.\n\n## Exceptions\n\n - `DetaError`: Base exception when anything goes wrong.\n - `ItemNotFound`: Fairly self-explanatory...\n - `InvalidDetaQuery`: Something is wrong with queries. Make sure you aren't using\n queries with unsupported types\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frickh94%2Fodetam","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frickh94%2Fodetam","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frickh94%2Fodetam/lists"}