Skip to main content

Pythonic DynamoDB ORM

Project description

Prismarine - DynamoDB ORM

Prismarine is a Pythonic ORM for DynamoDB, designed to simplify interactions with DynamoDB by providing a structured and Python-friendly interface. It leverages Python's type hinting and decorators to define models, which are then used to generate client code for database operations.

Key features include:

  • Model Definition: Models are defined using Python's TypedDict and are decorated with the Cluster.model decorator to specify primary and sort keys.
  • Automatic Client Generation: The prismarine_client.py file is auto-generated, containing classes and methods for interacting with DynamoDB tables based on the defined models.
  • Easy Integration: The generated client code integrates seamlessly with existing Python applications, providing methods for common database operations.

Prismarine aims to streamline the development process by reducing boilerplate code and ensuring that database interactions are type-safe and maintainable.

Prismarine works best with EasySAM.

Installation

pip install prismarine

Quick Overview

Expected Directory Structure:

<base-path>/
  <package-name>/
    - models.py
    - db.py
    - prismarine_client.py // Auto-generated

Models are defined in the models.py file. Each model is a TypedDict, decorated with the Cluster.model decorator.

The Cluster class is used to group extension models together. It also sets a prefix for the table names.

from typing import TypedDict, NotRequired
from prismarine import Cluster

c = Cluster('TapgameExample')

@c.model(PK='Foo', SK='Bar')
class Team(TypedDict):
    Foo: str
    Bar: str
    Baz: NotRequired[str]

If we place this code in <base-path>/<package-name>/models.py and the following command is run, it will generate a prismarine_client.py file in the same directory:

prismarine generate-client --base <base-path> <package-name>

The prismarine_client.py file will contain the following code:

class TeamModel(Model):
    table_name = 'TapgameExampleTeam'
    PK = 'Foo'
    SK = 'Bar'

    class UpdateDTO(TypedDict, total=False):
        Foo: str
        Bar: str
        Baz: NotRequired[str]

    @staticmethod
    def list(*, foo: str) -> List[Team]:
        ...

    @staticmethod
    def get(*, bar: str, foo: str, default: Team | EllipsisType = ...) -> Team:
        ...

    @staticmethod
    def put(team: Team) -> Team:
        ...

    @staticmethod
    def update(
        team: UpdateDTO, *, foo: str, bar: str, default: Team | EllipsisType = ...
    ) -> Team:
        ...

    @staticmethod
    def save(updated: Team, *, original: Team | None = None) -> Team:
        ...

    @staticmethod
    def delete(*, bar: str, foo: str):
        ...

    @staticmethod
    def scan() -> List[Team]:
        ...

As you can see, the TeamModel class has static methods for all the CRUD operations. The UpdateDTO class is similar to the Team class, but all fields are optional.

Creating a db.py File

Now, let's create a db.py file in the same directory:

import example.prismarine_client as pc

class TeamModel(pc.TeamModel):
    pass

Although you can import and use prismarine_client.py directly, it is recommended to create a db.py file that imports the generated client and extends it with your own methods.

You can now use the TeamModel class in your code:

from sam.common.example.db import TeamModel
from sam.common.prismarine import DbNotFound

# Create a new team
new_team = TeamModel.put({'Foo': 'foo', 'Bar': 'bar', 'Baz': 'baz'})

# List teams by a primary key
teams_by_foo = TeamModel.list(foo='foo')

# Get a team
try:
    team = TeamModel.get(foo='foo', bar='bar')
except DbNotFound:
    print('Team not found')

# Update a team
updated_team = TeamModel.update(
    {'Baz': 'new_baz'},
    foo='foo',
    bar='bar'
)

# List all teams
all_teams = TeamModel.scan()

# Delete a team
TeamModel.delete(foo='foo', bar='bar')

You may notice that Prismarine mostly requires named arguments. This ensures that changes to field names do not cause silent code failures. For example, if the Sort Key name is changed, all usages of get and update methods will break and be highlighted by the IDE and linter. This approach also makes the code more readable.

Advanced Usage

model Decorator

Aside from the PK and SK arguments, the Cluster.model decorator also accepts table and name arguments. table sets a full custom table name, while name sets a custom model name. For example, if the Cluster has a prefix TapgameExample, by default the Team model will have the table name TapgameExampleTeam. If we set name='Custom', the table name will be TapgameExampleCustom. And if we set table='CustomTable', the table name will simply be CustomTable, without the prefix.

index Decorator

index decorators must be used before the model decorator.

The Cluster.index decorator is used to define a secondary index. It accepts PK, SK, and index arguments.

@c.index(index='by-bar', PK='Bar', SK='Foo')
@c.model(PK='Foo', SK='Bar')
class Team(TypedDict):
    Foo: str
    Bar: str
    Baz: NotRequired[str]

This will add a subclass ByBar to the TeamModel class:

class TeamModel(Model):
    ...

    class ByBar:
        PK = 'Bar'
        SK = 'Foo'

        @staticmethod
        def list(
            *,
            bar: str,
            limit: int | None = None,
            direction: Literal['ASC', 'DESC'] = 'ASC'
        ) -> List[Team]:
            ...

        @staticmethod
        def get(*, bar: str, foo: str) -> Team:
            ...

export Decorator

The Cluster.export decorator is used to define a class that is not a model, but is exported from the cluster. It accepts a class as an argument. It is required to used on all classes that serve as types for model elements.

@c.export
class Team(TypedDict):
    Foo: str
    Bar: str

Other Commands

version

Prints the version of Prismarine.

prismarine version

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

prismarine-1.3.0.tar.gz (35.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

prismarine-1.3.0-py3-none-any.whl (12.7 kB view details)

Uploaded Python 3

File details

Details for the file prismarine-1.3.0.tar.gz.

File metadata

  • Download URL: prismarine-1.3.0.tar.gz
  • Upload date:
  • Size: 35.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.1

File hashes

Hashes for prismarine-1.3.0.tar.gz
Algorithm Hash digest
SHA256 ccef7667b347f7fc9c9ed3b20a0ca11edf1193259a18810c45c0ffb13e167bf4
MD5 ab738e9854573d0a55f3d39f8a025ef1
BLAKE2b-256 2c635c2282174a7ec489f198a86abefd42dc5b23a3fde88c88f182701cf2888a

See more details on using hashes here.

File details

Details for the file prismarine-1.3.0-py3-none-any.whl.

File metadata

  • Download URL: prismarine-1.3.0-py3-none-any.whl
  • Upload date:
  • Size: 12.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.1

File hashes

Hashes for prismarine-1.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 cd4ae1ea0b451676bc65edfdc7ffa7e7e567f9ae2367451a0791937f1e6a4d87
MD5 1383312d9eec1eca41060ca0bbf89d9f
BLAKE2b-256 0688a59f9b176ede25b23f9eb0805c25bf5da9de588ed73f062084df21d16100

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