Skip to content

Commit

Permalink
added the ability to set and using environment variables. Added exa… (#…
Browse files Browse the repository at this point in the history
…17)

* added the ability to set  and  using environment variables. Added example of dynamic  in readme

---------

Co-authored-by: Naya Verdier <[email protected]>
  • Loading branch information
peterb154 and nayaverdier authored Dec 22, 2023
1 parent b58c010 commit 7082cf5
Show file tree
Hide file tree
Showing 6 changed files with 180 additions and 10 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ coverage.xml

# mypy
.mypy_cache/
.idea
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 0.14.0 2023-12-21

- Add support for `__table_region__` and `__table_host__` to be lazy callables
- Default `__table_region__` and `__table_host__` to `DYNTASTIC_REGION` and
`DYNTASTIC_HOST` environment variable if not otherwise defined

## 0.13.1 2023-11-21

- Fix import error when using `pydantic>=2.5`
Expand Down
59 changes: 55 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,6 @@ p.model_dump_json()

```

To explicitly define an AWS region or DynamoDB endpoint url (for using a local
dynamodb docker instance, for example), set `__table_region__` or
`__table_host__`

### Inserting into DynamoDB

Using the `Product` example from above, simply:
Expand Down Expand Up @@ -362,6 +358,61 @@ index2 = Index("my_field", "my_second_field", index_name="my_field_my_second_fie
MyModel.create_table(index1, index2)
```

## Dynamic table names
In some circumstances you may want the table name to be defined dynamically.
This can be done by setting the `__table_name__` attribute to a Callable that returns the table name
from the source of your choice. In the example below, we are using an environment variable.

```python
import os
from dyntastic import Dyntastic

os.environ["MY_TABLE_NAME"] = "my_table"

class Product(Dyntastic):
__table_name__ = lambda: os.getenv("MY_TABLE_NAME")
__hash_key__ = "product_id"

product_id: str
```

## Custom dynamodb endpoint or region for local development
To explicitly define an AWS region or DynamoDB endpoint url (for using a local
dynamodb docker instance, for example), set `__table_region__` or `__table_host__`.
These attributes can be a string or a Callable that returns a string.

```python
from dyntastic import Dyntastic

class Product(Dyntastic):
__table_name__ = "products"
__table_region__ = "us-east-1"
__table_host__ = "http://localhost:8000"
__hash_key__ = "product_id"

product_id: str
```

You can also set the environment variables `DYNTASTIC_HOST` and/or `DYNTASTIC_REGION` to control the behavior
of the underlying boto3 client and resource objects.

*Note*: if both the environment variables and the class attributes are set,
the class attributes will take precedence.

```python
import os
from dyntastic import Dyntastic

os.environ["DYNTASTIC_HOST"] = "http://localhost:8000"
os.environ["DYNTASTIC_REGION"] = "us-east-1"

class Product(Dyntastic):
__table_name__ = "products"
__hash_key__ = "product_id"

product_id: str
```

## Contributing / Developer Setup

Make sure [`just`](https://github.com/casey/just) is installed on your system
Expand Down
26 changes: 21 additions & 5 deletions dyntastic/main.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os
import time
import warnings
from decimal import Decimal
Expand Down Expand Up @@ -25,7 +26,6 @@

__version__ = _metadata.version("dyntastic")


_T = TypeVar("_T", bound="Dyntastic")


Expand Down Expand Up @@ -441,6 +441,20 @@ def _resolve_table_name(cls) -> str:
else:
return cls.__table_name__

@classmethod
def _resolve_table_region(cls) -> Optional[str]:
if callable(cls.__table_region__):
return cls.__table_region__()
else:
return cls.__table_region__ or os.getenv("DYNTASTIC_REGION")

@classmethod
def _resolve_table_host(cls) -> Optional[str]:
if callable(cls.__table_host__):
return cls.__table_host__()
else:
return cls.__table_host__ or os.getenv("DYNTASTIC_HOST")

@classmethod
def _dynamodb_type(cls, key: str) -> str:
# Note: pragma nocover on the following line as coverage marks the ->exit branch as
Expand Down Expand Up @@ -483,11 +497,13 @@ def _dyntastic_key_dict(self):
def _dynamodb_boto3_kwargs(cls):
kwargs = {}

if cls.__table_region__:
kwargs["region_name"] = cls.__table_region__
region = cls._resolve_table_region()
if region:
kwargs["region_name"] = region

if cls.__table_host__:
kwargs["endpoint_url"] = cls.__table_host__
host = cls._resolve_table_host()
if host:
kwargs["endpoint_url"] = host

return kwargs

Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@

setup(
name="dyntastic",
version="0.13.1",
version="0.14.0",
description=description,
long_description=long_description,
long_description_content_type="text/markdown",
Expand Down
96 changes: 96 additions & 0 deletions tests/test_subclass.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from unittest.mock import patch

import pytest
from pydantic import Field

Expand Down Expand Up @@ -108,3 +110,97 @@ class MyObject(Dyntastic):
resource = MyObject._dynamodb_resource()
assert client.meta.endpoint_url == "http://localhost:8000"
assert resource.meta.client.meta.endpoint_url == "http://localhost:8000"


@patch.dict("os.environ", {"DYNTASTIC_HOST": "http://localhost:8000"})
def test_table_host_env():
class MyObject(Dyntastic):
__table_name__ = "my_object"
__hash_key__ = "my_hash_key"

my_hash_key: str

client = MyObject._dynamodb_client()
resource = MyObject._dynamodb_resource()
assert client.meta.endpoint_url == "http://localhost:8000"
assert resource.meta.client.meta.endpoint_url == "http://localhost:8000"


@patch.dict("os.environ", {"DYNTASTIC_HOST": "http://some-other-host"})
def test_table_host_meta_and_env():
class MyObject(Dyntastic):
__table_name__ = "my_object"
__hash_key__ = "my_hash_key"
__table_host__ = "http://localhost:8000"

my_hash_key: str

assert MyObject.__table_host__ == "http://localhost:8000"
client = MyObject._dynamodb_client()
resource = MyObject._dynamodb_resource()
assert client.meta.endpoint_url == "http://localhost:8000"
assert resource.meta.client.meta.endpoint_url == "http://localhost:8000"


def test_table_region():
class MyObject(Dyntastic):
__table_name__ = "my_object"
__hash_key__ = "my_hash_key"
__table_region__ = "fake-region"

my_hash_key: str

assert MyObject.__table_region__ == "fake-region"
client = MyObject._dynamodb_client()
resource = MyObject._dynamodb_resource()
assert client.meta.region_name == "fake-region"
assert resource.meta.client.meta.region_name == "fake-region"


@patch.dict("os.environ", {"DYNTASTIC_REGION": "fake-region"})
def test_table_region_env():
class MyObject(Dyntastic):
__table_name__ = "my_object"
__hash_key__ = "my_hash_key"

my_hash_key: str

client = MyObject._dynamodb_client()
resource = MyObject._dynamodb_resource()
assert client.meta.region_name == "fake-region"
assert resource.meta.client.meta.region_name == "fake-region"


@patch.dict("os.environ", {"DYNTASTIC_REGION": "other-region"})
def test_table_region_meta_and_env():
class MyObject(Dyntastic):
__table_name__ = "my_object"
__hash_key__ = "my_hash_key"
__table_region__ = "fake-region"

my_hash_key: str

assert MyObject.__table_region__ == "fake-region"
client = MyObject._dynamodb_client()
resource = MyObject._dynamodb_resource()
assert client.meta.region_name == "fake-region"
assert resource.meta.client.meta.region_name == "fake-region"


def test_table_host_region_callable():
"""Disabling flake rule "E371: do not assign a lambda expression, use a def" for this test"""

class MyObject(Dyntastic):
__table_name__ = "my_object"
__hash_key__ = "my_hash_key"
__table_host__ = lambda: "http://localhost:8000" # noqa #E371 do not assign a lambda expression, use a def
__table_region__ = lambda: "fake-region" # noqa #E371 do not assign a lambda expression, use a def

my_hash_key: str

client = MyObject._dynamodb_client()
resource = MyObject._dynamodb_resource()
assert client.meta.endpoint_url == "http://localhost:8000"
assert resource.meta.client.meta.endpoint_url == "http://localhost:8000"
assert client.meta.region_name == "fake-region"
assert resource.meta.client.meta.region_name == "fake-region"

0 comments on commit 7082cf5

Please sign in to comment.