Module xdynamo.common_types

Global variables

var operator_alias_map

Used to normalize some common operators (that we use in other systems) to the one used in Dynamo.

Functions

def get_dynamo_type_from_python_type(some_type: Type) ‑> str

Classes

class DynKey (api: DynApi, id: str = None, hash_key: Any = None, range_key: Optional[Any] = None, range_operator: str = None, require_full_key: bool = True)

DynKey(api: 'DynApi', id: str = None, hash_key: Any = None, range_key: Optional[Any] = None, range_operator: str = None, require_full_key: bool = True)

Expand source code
@dataclasses.dataclass(frozen=True, eq=True)
class DynKey:
    api: 'DynApi' = dataclasses.field(compare=False)
    # We only compare with `id`, this should represent our identity sufficiently.
    id: str = None
    hash_key: Union[Any] = dataclasses.field(default=None, compare=False)
    range_key: Optional[Any] = (
        dataclasses.field(default=None, compare=False)
    )
    range_operator: str = dataclasses.field(default=None, compare=False)
    require_full_key: bool = dataclasses.field(default=True, compare=False)

    def __str__(self):
        return self.id or ''

    @classmethod
    def via_obj(cls, obj: 'DynModel') -> 'DynKey':
        structure = obj.api.structure
        hash_name = structure.dyn_hash_key_name

        if not hash_name:
            raise XModelDynamoNoHashKeyDefinedError(
                f"While constructing {structure.model_cls}, found no hash-key field. "
                f"You must have at least one hash-key field."
            )

        hash_value = getattr(obj, hash_name)

        if hash_value is None:
            raise XModelDynamoError(
                f"Unable to get DynKey due to `None` for dynamo hash-key ({hash_value}) "
                f"on object {obj}."
            )

        range_name = structure.dyn_range_key_name
        range_value = None
        if range_name:
            range_value = getattr(obj, range_name)
            if range_value is None:
                raise XModelDynamoError(
                    f"Unable to get DynKey due to `None` for dynamo range-key ({range_name}) "
                    f"on object {obj}."
                )

        return DynKey(api=obj.api, hash_key=hash_value, range_key=range_value)

    def key_as_dict(self):
        structure = self.api.structure
        hash_field = structure.dyn_hash_field
        range_field = structure.dyn_range_field

        def run_converter(field: 'DynField', value) -> Any:
            converter = field.converter
            if not converter:
                return value

            return converter(
                self.api,
                Converter.Direction.to_json,
                field,
                value
            )

        # Append the keys for the items we want into what we will request.
        item_request = {hash_field.name: run_converter(hash_field, self.hash_key)}
        if range_field:
            item_request[range_field.name] = run_converter(range_field, self.range_key)
        return item_request

    def __post_init__(self):
        structure = self.api.structure
        delimiter = structure.dyn_id_delimiter
        range_name = structure.dyn_range_key_name
        need_range_key = bool(range_name)

        hash_key = self.hash_key
        range_key = self.range_key
        api = self.api

        _id = self.id
        if _id is not None and not isinstance(_id, str):
            # `self.id` must always be a string.
            # todo: Must check for standard converter method
            _id = str(_id)
            object.__setattr__(self, 'id', _id)

        require_full_key = self.require_full_key

        # First, figure out `self.id` if not provided.
        if not _id:
            if not hash_key:
                raise XRemoteError(
                    f"Tried to create DynKey with no id ({_id}) or no hash key ({hash_key})."
                )

            if require_full_key and need_range_key and not range_key:
                raise XRemoteError(
                    f"Tried to create DynKey with no id ({_id}) or no range key ({range_key})."
                )

            key_names = [(structure.dyn_hash_key_name, hash_key)]
            # Generate ID without delimiter to represent an entire hash-page (ie: any range value)
            if need_range_key and range_key is not None:
                key_names.append((range_name, range_key))

            keys = []
            for key_name, key_value in key_names:
                field = structure.get_field(key_name)
                converter = field.converter
                final_value = key_value
                if converter:
                    if self.range_operator == 'between' and isinstance(key_value, list):
                        sub_v_result = []
                        for sub_v in key_value:
                            sub_v = converter(
                                api,
                                Converter.Direction.to_json,
                                field,
                                sub_v
                            )
                            sub_v_result.append(str(sub_v))
                        final_value = ",".join(sub_v_result)
                    else:
                        final_value = converter(
                            api,
                            Converter.Direction.to_json,
                            field,
                            key_value
                        )
                keys.append(final_value)

            _id = delimiter.join([str(x) for x in keys])
            object.__setattr__(self, 'id', _id)
        elif need_range_key and delimiter not in _id:
            raise XRemoteError(
                f"Tried to create DynKey with an `id` ({_id}) "
                f"that did not have delimiter ({delimiter}) in it. "
                f"This means we are missing the range-key part for field ({range_name}) "
                f"in the `id` that was provided.  Trying providing the id like this: "
                f'"{_id}{delimiter}'
                r'{range-key-value-goes-here}".'  # <-- want to directly-output the `{` part.
            )

        # If we got provided a hash-key directly, no need to continue any farther.
        if hash_key:
            if require_full_key and need_range_key and not range_key:
                raise XRemoteError(
                    f"Have hash_key ({hash_key}) without needed range_key while creating DynKey."
                )
            # We were provided the hash/range key already, as an optimization I don't use time
            # checking to see if they passed in the same values that they may have passed in `id`.
            return

        # They did not pass in hash_key, so we must parse the `id` they provided
        # and then set them on self.

        if not need_range_key:
            # If we don't need range key, there is no delimiter to look for.
            hash_key = _id
        else:
            split_id = _id.split(delimiter)
            if len(split_id) != 2:
                raise XRemoteError(
                    f"For dynamo table ({self.api.model_type}): Have id ({_id}) but delimiter "
                    f"({delimiter}) is either not present, or is in it more than once. "
                    f"'id' needs to contain exactly one hash and range key combined together "
                    f"with the delimiter, ie: 'hash-key-value{delimiter}range-key-value'. "
                    f"See xdynamo.dyn_connections documentation for more details on how "
                    f"this works."
                )
            # todo: Consider converting these `from_json`, like we convert `to_json`
            #   when we put the keys into the `id` (see above, where we generate `id` if needed)?
            hash_key = split_id[0]
            range_key = split_id[1]

        object.__setattr__(self, 'hash_key', hash_key)
        object.__setattr__(self, 'range_key', range_key)

Class variables

var api : DynApi
var hash_key : Any
var id : str
var range_key : Optional[Any]
var range_operator : str
var require_full_key : bool

Static methods

def via_obj(obj: DynModel)

Methods

def key_as_dict(self)
class DynKeyType (value, names=None, *, module=None, qualname=None, type=None, start=1)

Possible values for field option keys.

Expand source code
class DynKeyType(Enum):
    """ Possible values for field option keys. """
    hash = EnumAuto()
    range = EnumAuto()

Ancestors

  • enum.Enum

Class variables

var hash
var range