-
Notifications
You must be signed in to change notification settings - Fork 429
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(dynamo_documents): propagate delete to dynamo (#3220)
- Loading branch information
1 parent
79851ce
commit b7ecd75
Showing
26 changed files
with
642 additions
and
208 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
from .environment_api_key_wrapper import DynamoEnvironmentAPIKeyWrapper | ||
from .environment_wrapper import ( | ||
DynamoEnvironmentV2Wrapper, | ||
DynamoEnvironmentWrapper, | ||
) | ||
from .identity_wrapper import DynamoIdentityWrapper | ||
|
||
__all__ = ( | ||
"DynamoEnvironmentAPIKeyWrapper", | ||
"DynamoEnvironmentV2Wrapper", | ||
"DynamoEnvironmentWrapper", | ||
"DynamoIdentityWrapper", | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import typing | ||
|
||
import boto3 | ||
from botocore.config import Config | ||
|
||
if typing.TYPE_CHECKING: | ||
from mypy_boto3_dynamodb.service_resource import Table | ||
|
||
|
||
class BaseDynamoWrapper: | ||
table_name: str = None | ||
|
||
def __init__(self) -> None: | ||
self._table: typing.Optional["Table"] = None | ||
|
||
@property | ||
def table(self) -> typing.Optional["Table"]: | ||
if not self._table: | ||
self._table = self.get_table() | ||
return self._table | ||
|
||
def get_table_name(self) -> str: | ||
return self.table_name | ||
|
||
def get_table(self) -> typing.Optional["Table"]: | ||
if table_name := self.get_table_name(): | ||
return boto3.resource("dynamodb", config=Config(tcp_keepalive=True)).Table( | ||
table_name | ||
) | ||
|
||
@property | ||
def is_enabled(self) -> bool: | ||
return self.table is not None | ||
|
||
def query_get_all_items(self, **kwargs: dict) -> typing.Generator[dict, None, None]: | ||
while True: | ||
query_response = self.table.query(**kwargs) | ||
for item in query_response["Items"]: | ||
yield item | ||
|
||
last_evaluated_key = query_response.get("LastEvaluatedKey") | ||
if not last_evaluated_key: | ||
break | ||
|
||
kwargs["ExclusiveStartKey"] = last_evaluated_key |
31 changes: 31 additions & 0 deletions
31
api/environments/dynamodb/wrappers/environment_api_key_wrapper.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import typing | ||
|
||
from django.conf import settings | ||
|
||
from util.mappers import ( | ||
map_environment_api_key_to_environment_api_key_document, | ||
) | ||
|
||
from .base import BaseDynamoWrapper | ||
|
||
if typing.TYPE_CHECKING: | ||
from environments.models import EnvironmentAPIKey | ||
|
||
|
||
class DynamoEnvironmentAPIKeyWrapper(BaseDynamoWrapper): | ||
table_name = settings.ENVIRONMENTS_API_KEY_TABLE_NAME_DYNAMO | ||
|
||
def write_api_key(self, api_key: "EnvironmentAPIKey"): | ||
self.write_api_keys([api_key]) | ||
|
||
def write_api_keys(self, api_keys: typing.Iterable["EnvironmentAPIKey"]): | ||
with self.table.batch_writer() as writer: | ||
for api_key in api_keys: | ||
writer.put_item( | ||
Item=map_environment_api_key_to_environment_api_key_document( | ||
api_key | ||
) | ||
) | ||
|
||
def delete_api_key(self, key: str) -> None: | ||
self.table.delete_item(Key={"key": key}) |
129 changes: 129 additions & 0 deletions
129
api/environments/dynamodb/wrappers/environment_wrapper.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
import typing | ||
from typing import Any, Iterable | ||
|
||
from boto3.dynamodb.conditions import Key | ||
from django.conf import settings | ||
from django.core.exceptions import ObjectDoesNotExist | ||
|
||
from environments.dynamodb.constants import ( | ||
DYNAMODB_MAX_BATCH_WRITE_ITEM_COUNT, | ||
ENVIRONMENTS_V2_PARTITION_KEY, | ||
ENVIRONMENTS_V2_SORT_KEY, | ||
) | ||
from environments.dynamodb.types import IdentityOverridesV2Changeset | ||
from environments.dynamodb.utils import ( | ||
get_environments_v2_identity_override_document_key, | ||
) | ||
from util.mappers import ( | ||
map_environment_to_environment_document, | ||
map_environment_to_environment_v2_document, | ||
map_identity_override_to_identity_override_document, | ||
) | ||
from util.util import iter_paired_chunks | ||
|
||
from .base import BaseDynamoWrapper | ||
|
||
if typing.TYPE_CHECKING: | ||
from mypy_boto3_dynamodb.type_defs import QueryInputRequestTypeDef | ||
|
||
from environments.models import Environment | ||
|
||
|
||
class BaseDynamoEnvironmentWrapper(BaseDynamoWrapper): | ||
def write_environment(self, environment: "Environment") -> None: | ||
self.write_environments([environment]) | ||
|
||
def write_environments(self, environments: Iterable["Environment"]) -> None: | ||
raise NotImplementedError() | ||
|
||
|
||
class DynamoEnvironmentWrapper(BaseDynamoEnvironmentWrapper): | ||
table_name = settings.ENVIRONMENTS_TABLE_NAME_DYNAMO | ||
|
||
def write_environments(self, environments: Iterable["Environment"]): | ||
with self.table.batch_writer() as writer: | ||
for environment in environments: | ||
writer.put_item( | ||
Item=map_environment_to_environment_document(environment), | ||
) | ||
|
||
def get_item(self, api_key: str) -> dict: | ||
try: | ||
return self.table.get_item(Key={"api_key": api_key})["Item"] | ||
except KeyError as e: | ||
raise ObjectDoesNotExist() from e | ||
|
||
def delete_environment(self, api_key: str) -> None: | ||
self.table.delete_item(Key={"api_key": api_key}) | ||
|
||
|
||
class DynamoEnvironmentV2Wrapper(BaseDynamoEnvironmentWrapper): | ||
def get_table_name(self) -> str | None: | ||
return settings.ENVIRONMENTS_V2_TABLE_NAME_DYNAMO | ||
|
||
def get_identity_overrides_by_environment_id( | ||
self, | ||
environment_id: int, | ||
feature_id: int | None = None, | ||
) -> typing.List[dict[str, Any]]: | ||
try: | ||
response = self.table.query( | ||
KeyConditionExpression=Key(ENVIRONMENTS_V2_PARTITION_KEY).eq( | ||
str(environment_id), | ||
) | ||
& Key(ENVIRONMENTS_V2_SORT_KEY).begins_with( | ||
get_environments_v2_identity_override_document_key( | ||
feature_id=feature_id, | ||
), | ||
) | ||
) | ||
return response["Items"] | ||
except KeyError as e: | ||
raise ObjectDoesNotExist() from e | ||
|
||
def update_identity_overrides( | ||
self, | ||
changeset: IdentityOverridesV2Changeset, | ||
) -> None: | ||
for to_put, to_delete in iter_paired_chunks( | ||
changeset.to_put, | ||
changeset.to_delete, | ||
chunk_size=DYNAMODB_MAX_BATCH_WRITE_ITEM_COUNT, | ||
): | ||
with self.table.batch_writer() as writer: | ||
for identity_override_to_delete in to_delete: | ||
writer.delete_item( | ||
Key={ | ||
ENVIRONMENTS_V2_PARTITION_KEY: identity_override_to_delete.environment_id, | ||
ENVIRONMENTS_V2_SORT_KEY: identity_override_to_delete.document_key, | ||
}, | ||
) | ||
for identity_override_to_put in to_put: | ||
writer.put_item( | ||
Item=map_identity_override_to_identity_override_document( | ||
identity_override_to_put | ||
), | ||
) | ||
|
||
def write_environments(self, environments: Iterable["Environment"]) -> None: | ||
with self.table.batch_writer() as writer: | ||
for environment in environments: | ||
writer.put_item( | ||
Item=map_environment_to_environment_v2_document(environment), | ||
) | ||
|
||
def delete_environment(self, environment_id: int): | ||
environment_id = str(environment_id) | ||
filter_expression = Key(ENVIRONMENTS_V2_PARTITION_KEY).eq(environment_id) | ||
query_kwargs: "QueryInputRequestTypeDef" = { | ||
"KeyConditionExpression": filter_expression, | ||
"ProjectionExpression": "document_key", | ||
} | ||
with self.table.batch_writer() as writer: | ||
for item in self.query_get_all_items(**query_kwargs): | ||
writer.delete_item( | ||
Key={ | ||
ENVIRONMENTS_V2_PARTITION_KEY: environment_id, | ||
ENVIRONMENTS_V2_SORT_KEY: item["document_key"], | ||
}, | ||
) |
Oops, something went wrong.
b7ecd75
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
flagsmith-frontend-staging – ./frontend
staging.flagsmith.com
flagsmith-frontend-staging-git-main-flagsmith.vercel.app
flagsmith-staging-frontend.vercel.app
flagsmith-frontend-staging-flagsmith.vercel.app
b7ecd75
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
flagsmith-frontend-preview – ./frontend
flagsmith-frontend-production-native.vercel.app
flagsmith-frontend-preview-git-main-flagsmith.vercel.app
flagsmith-frontend-preview-flagsmith.vercel.app
b7ecd75
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
docs – ./docs
docs-git-main-flagsmith.vercel.app
docs-flagsmith.vercel.app
docs.bullet-train.io
docs.flagsmith.com