Skip to main content

ODetaM

Test codecov

A simple ODM (Object Document Mapper) for Deta Base base on pydantic.

Installation

pip install odetam

Usage

Create pydantic models as normal, but inherit from DetaModel instead of pydantic BaseModel. You will need to set the environment variable DETA_PROJECT_KEY to your Deta project key so that databases can be accessed/created, instead you are working under deta initialized project. Your also can specify Deta project key in Config class of your model, for migration from Deta Cloud or importing external Collection (read DetaBase Docs) This is a secret key, so handle it appropriately (hence the environment variable).

Bases will be automatically created based on model names (changed from PascalCase/CamelCase case to snake_case). A key field (Deta's unique id) will be automatically added to any model. You can supply the key on creation, or Deta will generate one automatically and it will be added to the object when it is saved.

Async Support

Async/await is now supported! As of version 1.2.0, you can now from odetam.async_model import AsyncDetaModel, inherit from that, and run all the examples below just the same, but with await in front of the calls.

You must pip install deta[async], to use asynchronous base.

Get All

DetaModel.get_all() should handle large bases better now, but you should consider querying instead of getting everything if possible, because it is unlikely to perform well on large bases.

Example

Basics

import datetime
from typing import List

from odetam import DetaModel


class Captain(DetaModel):
    name: str
    joined: datetime.date
    ships: List[str]


# create
kirk = Captain(
        name="James T. Kirk",
        joined=datetime.date(2252, 1, 1),
        ships=["Enterprise"],
        )

sisko = Captain(
        name="Benjamin Sisko",
        joined=datetime.date(2350, 1, 1),
        ships=["Deep Space 9", "Defiant"],
        )

# initial save, key is now set
kirk.save()

# update the object
kirk.ships.append("Enterprise-A")

# save again, this will be an update
kirk.save()

sisko.save()

Captain.get_all()
# [
#     Captain(
#         name="James T. Kirk", 
#         joined=datetime.date(2252, 01, 01), 
#         ships=["Enterprise", "Enterprise-A"],
#         key="key1",
#     ),
#     Captain(
#         name="Benjamin Sisko",
#         joined=datetime.date(2350, 01, 01), 
#         ships=["Deep Space 9", "Defiant"],
#         key="key2",
#     ),
# ]

Captain.get("key1")
# Captain(
#     name="James T. Kirk", 
#     joined=datetime.date(2252, 01, 01), 
#     ships=["Enterprise", "Enterprise-A"],
#     key="key1",
# )

Captain.get("key3")
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# odetam.exceptions.ItemNotFound

Captain.get_or_none("key3")
# None

Captain.query(Captain.name == "James T. Kirk")
# Captain(
#     name="James T. Kirk", 
#     joined=datetime.date(2252, 01, 01), 
#     ships=["Enterprise", "Enterprise-A"],
#     key="key1",
# )

Captain.query(Captain.ships.contains("Defiant"))
# Captain(
#     name="Benjamin Sisko",
#     joined=datetime.date(2350, 01, 01),
#     ships=["Deep Space 9", "Defiant"],
# )

Captain.query(Captain.name.prefix("Ben"))
# Captain(
#     name="Benjamin Sisko",
#     joined=datetime.date(2350, 01, 01),
#     ships=["Deep Space 9", "Defiant"],
# )

kirk.delete()
Captain.delete_key("key2")

Captain.get_all()
# []

# you can also save several at once for better speed
Captain.put_many([kirk, sisko])
# [
#     Captain(
#         name="James T. Kirk", 
#         joined=datetime.date(2252, 01, 01), 
#         ships=["Enterprise", "Enterprise-A"],
#         key="key1",
#     ),
#     Captain(
#         name="Benjamin Sisko",
#         joined=datetime.date(2350, 01, 01), 
#         ships=["Deep Space 9", "Defiant"],
#         key="key2",
#     ),
# ]

Async model

import datetime
from typing import List

from odetam.async_model import AsyncDetaModel


class Captain(AsyncDetaModel):
    name: str
    joined: datetime.date
    ships: List[str]


async foo():
    items = await Captain.get_all()

Config

class Captain(AsyncDetaModel):
    name: str
    joined: datetime.date
    ships: List[str]

    class Config:
        table_name = "my_custom_table_name"
        deta_key = "123_123" # project key from Deta Cloud or Data Key from another Deta Space project

Save

Models have the .save() method which will always behave as an upsert, updating a record if it has a key, otherwise creating it and setting a key. Deta has pure insert behavior, but it's less performant. If you need it, please open a pull request.

Querying

All basic comparison operators are implemented to map to their equivalents as (Model.field >= comparison_value). There is also a .contains() and .not_contains() method for strings and lists of strings, as well as a .prefix() method for strings. There is also a .range() for number types that takes a lower and upper bound. You can also use & as AND and | as OR. ORs cannot be nested within ands, use a list of options as comparison instead. You can use as many ORs as you want, as long as they execute after the ANDs in the order of operations. This is due to how the Deta Base api works.

Deta Base

Direct access to the base is available in the dunder attribute __db__, though the point is to avoid that.

Exceptions

  • DetaError: Base exception when anything goes wrong.
  • ItemNotFound: Fairly self-explanatory...
  • InvalidDetaQuery: Something is wrong with queries. Make sure you aren't using queries with unsupported types

Metadata

Release files for odetam 1.5.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for odetam 1.5.2
File Size Uploaded Provenance Actions
odetam-1.5.2.tar.gz 9.7 kB Without provenance Details for odetam-1.5.2.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for odetam 1.5.2
File Interpreter ABI Platform Actions
odetam-1.5.2-py3-none-any.whl Python 3 none any Details for odetam-1.5.2-py3-none-any.whl

Total release size: 20.0 kB

Release files / odetam-1.5.2.tar.gz

Download URL odetam-1.5.2.tar.gz
Size 9.7 kB
Tags Source
SHA-256 checksum
How to use checksums
3d111c060f41b449ad1fdbc0f2d27ec66e0dce06d668c14ad4661bfa9b4e5706
BLAKE2b-256 checksum
How to use checksums
8868127d5bb9a3e1ac9369ac65a68ef0b6421d3d07a22e0dd5bcc8b523af4b6e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via poetry/1.4.2 CPython/3.10.12 Darwin/23.0.0

Release files / odetam-1.5.2-py3-none-any.whl

Download URL odetam-1.5.2-py3-none-any.whl
Size 10.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
30a597083a467db03927fb939ca0814ac14f70e96327ea23c67fa45d69dfa981
BLAKE2b-256 checksum
How to use checksums
f7537960c78dc405489fe337231614ac7a276f3eeb7cec30f3d96e07631c0336
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via poetry/1.4.2 CPython/3.10.12 Darwin/23.0.0

Security for odetam 1.5.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page