Module xdynamo.structure
Classes
class DynStructure (*, parent: Optional[ForwardRef('RemoteStructure')], field_type: Type[~F])
-
Putting it here right now, it uses most of the super-class stuff, with a few extra fields. See
xmodel.base.structure.BaseStructure
for more details;Use these names on the DynModel's as class-arguments to configure these. Those values will eventually be put into this structure for use by the DynClient/DynApi classes.
Expand source code
class DynStructure(RemoteStructure[F]): """ Putting it here right now, it uses most of the super-class stuff, with a few extra fields. See `xmodel.base.structure.BaseStructure` for more details; Use these names on the DynModel's as class-arguments to configure these. Those values will eventually be put into this structure for use by the DynClient/DynApi classes. """ # Todo: Refer to parent docs, but put more info about how virtual-id's are used for our # dynamo tables. virtual_id: bool = True dyn_name: str = Default """ Table name, minus service/environment name. We generally want to use camelCase for this and no dashes/underscores. If left as `Default`, will use a "camelCase" version of the model class name. """ dyn_service: str = Default """ Service to use, by `Default` we use current `xcon.conf.XconSettings.service`. If Blank/None we won't include it in the name. """ dyn_environment: str = Default """ Environment to use, by `Default` we use current `xcon.conf.XconSettings.environment`. If Blank/None we won't include it in the name. """ dyn_hash_key_name: str = None """ Name of hash key [for now both model and table attribute must be same name]. If left as None, an error will be raised when we try connect to table. You can more easily indicate this via `HashField`, ie: >>> class MyModel(DynModel): ... my_hash: str = HashField() When you do this, we will fill in `dyn_hash_key_name` for you. """ dyn_range_key_name: str = None """ Name of range key [for now both model and table attribute must be same name]; If left as None, we don't have one on the table. You can more easily indicate this via `RangeField`, ie: >>> class MyModel(DynModel): ... my_hash: str = RangeField() When you do this, we will fill in `dyn_range_key_name` for you. """ dyn_consistent_read: bool = Default """ If True will default reads for the associated model to be consistent, Otherwise it won't use consistent reads by default. Individual gets/queries can override this default via function param. """ dyn_id_delimiter = "|" """ Default way to delimit the hash/range keys when used in external systems and with code under the `xmodel` library. In general, it's assumed we only have one key that can uniquely identify model objects/items in general. For DynamoDB, if it has a range key we can merge both the hash-key and range-key together with this character. By default it's a pipe `|`. But you can set it to something else here if needed. This makes it so other systems can store just one value to refer to an item in a dynamo table, ie: "some-hash-value|some-range-value". When other BaseModel's from other systems need to get a Dynamo model, they can look it up via this single 'id' value. """ @property def dyn_hash_field(self) -> DynField: return self.get_field(self.dyn_hash_key_name) @property def dyn_range_field(self) -> Optional[DynField]: return self.get_field(self.dyn_range_key_name) def has_id_field(self): id_field = self.get_field('id') return (id_field.dyn_key is DynKeyType.hash) if id_field else False def configure_for_model_type( self, *, # These fields are used to name the table # format: `{dyn_service}-{dyn_environment}-{dyn_name}` dyn_name: str = Default, dyn_service: str = Default, dyn_environment: str = Default, dyn_id_delimiter: str = Default, dyn_consistent_read: bool = Default, **kwargs ): """ See superclass method `xmodel.base.structure.BaseStructure.configure_for_model_type` first. It has more information about what calls this method and how to customize value via class arguments (see `xmodel.base.model.BaseModel.__init_subclass__` for more about the class argument angle). This method accepts every argument in the super-class version, with the addition of ones more specific to Dynamo. So se This method accepts named-class-parameters for `xdynamo.model.DynModel` subclasses. See super-class method `xmodel.base.structure.BaseStructure.configure_for_model_type` doc's for additional arguments and details. Args: dyn_name: Name of the table. We will add dyn_service and current environment to the final name. See `DynStructure.fully_qualified_table_name` for more details. This can also be `None` to indicate that it has no direct-table (ie: this is an abstract base class, with commonly shared fields, etc). dyn_service: Name of the service to use for that portion of the table name. If not provided, and `xcon` is available, will use `con.conf.XconSettings.service`. Right now `xcon` is a required dependency, but will make it optional in the future. In that future version, leaving this as `Default` would make it not format the service name into the table name. dyn_environment: Name of the environment to use for that portion of the table name. Right now `xcon` is a required dependency, but will make it optional in the future. In that future version, leaving this as `Default` would make it not format the environment name into the table name. dyn_id_delimiter: Delimiter to use for the `id` value when table has both a hash and range key (to delimit the values of the two). By default, a pipe char `|` is used. dyn_consistent_read: Lets model default to consistent reads by default. If unspecified, consistent reads are not used by default. You can override this on a per-get/query/scan basis via function param. **kwargs: These all come from class-arguments given to the `xdynamo.model.DynModel` at class-definition time that need to be sent to my super-class via `xmodel.base.structure.BaseStructure.configure_for_model_type`. See that for more on what other arguments are supported. """ super().configure_for_model_type(**kwargs) # Resolve default `dyn_name` if needed if dyn_name is Default: # Attempt to use model class name (with first char lower-cased) if we have one. model_name = self.model_cls.__name__ dyn_name = f'{model_name[:1].lower()}{model_name[1:]}' if model_name else '' if dyn_name == '': raise XRemoteError( f"The `dyn_name` of dynamo model ({self.model_cls}) was blank/None; " f"model must be `None` or have a non-blank `dyn_name`." ) self.dyn_name = dyn_name self.dyn_service = dyn_service self.dyn_environment = dyn_environment self.dyn_consistent_read = dyn_consistent_read if dyn_id_delimiter: self.dyn_id_delimiter = dyn_id_delimiter type_to_attr_map = { DynKeyType.hash: 'dyn_hash_key_name', DynKeyType.range: 'dyn_range_key_name', } encountered_types: Set[DynKeyType] = set() for field in self.fields: # We can assume all fields are DynField subclasses, since it's the field_type key_type = field.dyn_key if not key_type: continue attr_name = type_to_attr_map[key_type] if key_type in encountered_types: last_encountered_name = getattr(self, attr_name, None) raise XRemoteError( f"We have multiple ({DynKeyType.hash}) key-type fields: " f"({last_encountered_name}) and ({field.name}) for model ({self.model_cls}). " f"There can only be one ({DynKeyType.hash}) field key-type on a dynamo table." ) encountered_types.add(key_type) setattr(self, attr_name, field.name) # If user manually set this to 'True', then keep it; otherwise determine it based # on if there was a hash-key on 'id'. # If the `id` field is not the hash-key, then we don't consider us having a # `id` field. Instead, we have a virtual-id field that is a combination of both # the hash-key field value with the range-key value (if we have a range-key). # The purpose of that is to indicate that the `id` field is not a normal field # on this class, but a virtual property instead (see `DynModel.id`). from xdynamo.model import DynModel if self.model_cls is DynModel: # Restore `DynModel.id` normal non-field property for DynModel. # We want it to be a virtual-id. # (BaseModel by default will capture field properties into `fget` and `fset` # in the Field object, and then execute properties when needed after doing it's # normal thing.... we want to replace that functionality with our own property, # and not have it go through the normal things... but still treat 'id' like a # normal property otherwise). id_field = self.get_field('id') self.model_cls.id = property(fget=id_field.fget, fset=id_field.fset) def fully_qualified_table_name(self) -> str: """ Fully qualified name of the table in Dynamo as a str. Format is: '{dyn_service}-{dyn_environment}-{dyn_name}' """ components = [] service = self.dyn_service if service is Default: service = xcon_settings.service env = self.dyn_environment if env is Default: env = xcon_settings.environment if service: components.append(service) if env: components.append(env) name = self.dyn_name if not name: raise XRemoteError( f"Tried to get `fully_qualified_table_name` but have no table name for {self}" ) components.append(name) return "-".join(components) @property def endpoint_description(self): return self.fully_qualified_table_name()
Ancestors
- xmodel.remote.structure.RemoteStructure
- xmodel.base.structure.BaseStructure
- typing.Generic
Class variables
var dyn_consistent_read : bool
-
If True will default reads for the associated model to be consistent, Otherwise it won't use consistent reads by default.
Individual gets/queries can override this default via function param.
var dyn_environment : str
-
Environment to use, by
Default
we use currentxcon.conf.XconSettings.environment
. If Blank/None we won't include it in the name. var dyn_hash_key_name : str
-
Name of hash key [for now both model and table attribute must be same name]. If left as None, an error will be raised when we try connect to table.
You can more easily indicate this via
HashField
, ie:>>> class MyModel(DynModel): ... my_hash: str = HashField()
When you do this, we will fill in
dyn_hash_key_name
for you. var dyn_id_delimiter
-
Default way to delimit the hash/range keys when used in external systems and with code under the
xmodel
library. In general, it's assumed we only have one key that can uniquely identify model objects/items in general. For DynamoDB, if it has a range key we can merge both the hash-key and range-key together with this character. By default it's a pipe|
. But you can set it to something else here if needed.This makes it so other systems can store just one value to refer to an item in a dynamo table, ie: "some-hash-value|some-range-value". When other BaseModel's from other systems need to get a Dynamo model, they can look it up via this single 'id' value.
var dyn_name : str
-
Table name, minus service/environment name. We generally want to use camelCase for this and no dashes/underscores.
If left as
Default
, will use a "camelCase" version of the model class name. var dyn_range_key_name : str
-
Name of range key [for now both model and table attribute must be same name]; If left as None, we don't have one on the table.
You can more easily indicate this via
RangeField
, ie:>>> class MyModel(DynModel): ... my_hash: str = RangeField()
When you do this, we will fill in
dyn_range_key_name
for you. var dyn_service : str
-
Service to use, by
Default
we use currentxcon.conf.XconSettings.service
. If Blank/None we won't include it in the name. var virtual_id : bool
Instance variables
prop dyn_hash_field : DynField
-
Expand source code
@property def dyn_hash_field(self) -> DynField: return self.get_field(self.dyn_hash_key_name)
prop dyn_range_field : Optional[DynField]
-
Expand source code
@property def dyn_range_field(self) -> Optional[DynField]: return self.get_field(self.dyn_range_key_name)
prop endpoint_description
-
Gives some sort of basic descriptive string that contains the path/table-name/etc that basically indicates the api endpoint being used.
This is meant for logging and other human readability/debugging purposes. Feel free to change the string to whatever is most useful to know.
I expect this to be overridden by the concrete implementation, see examples here:
xmodel.rest.RestStructure.endpoint_description
xmodel.dynamo.DynStructure.endpoint_description
Expand source code
@property def endpoint_description(self): return self.fully_qualified_table_name()
Methods
def configure_for_model_type(self, *, dyn_name: str = Default, dyn_service: str = Default, dyn_environment: str = Default, dyn_id_delimiter: str = Default, dyn_consistent_read: bool = Default, **kwargs)
-
See superclass method
xmodel.base.structure.BaseStructure.configure_for_model_type
first. It has more information about what calls this method and how to customize value via class arguments (seexmodel.base.model.BaseModel.__init_subclass__
for more about the class argument angle).This method accepts every argument in the super-class version, with the addition of ones more specific to Dynamo. So se
This method accepts named-class-parameters for
DynModel
subclasses.See super-class method
xmodel.base.structure.BaseStructure.configure_for_model_type
doc's for additional arguments and details.Args
dyn_name
-
Name of the table. We will add dyn_service and current environment to the final name. See
DynStructure.fully_qualified_table_name()
for more details.This can also be
None
to indicate that it has no direct-table (ie: this is an abstract base class, with commonly shared fields, etc). dyn_service
-
Name of the service to use for that portion of the table name. If not provided, and
xcon
is available, will usecon.conf.XconSettings.service
. Right nowxcon
is a required dependency, but will make it optional in the future.In that future version, leaving this as
Default
would make it not format the service name into the table name. dyn_environment
-
Name of the environment to use for that portion of the table name. Right now
xcon
is a required dependency, but will make it optional in the future.In that future version, leaving this as
Default
would make it not format the environment name into the table name. dyn_id_delimiter
-
Delimiter to use for the
id
value when table has both a hash and range key (to delimit the values of the two).By default, a pipe char
|
is used. dyn_consistent_read
- Lets model default to consistent reads by default. If unspecified, consistent reads are not used by default. You can override this on a per-get/query/scan basis via function param.
**kwargs
- These all come from class-arguments given to the
DynModel
at class-definition time that need to be sent to my super-class viaxmodel.base.structure.BaseStructure.configure_for_model_type
. See that for more on what other arguments are supported.
def fully_qualified_table_name(self) ‑> str
-
Fully qualified name of the table in Dynamo as a str. Format is: '{dyn_service}-{dyn_environment}-{dyn_name}'
def has_id_field(self)
-
Defaults to False, returns True for RemoteStructure, What this property is really saying is if you can do a foreign-key to the related object/model.
It may be better at some point in the long-run to rename this field to more indicate that; perhaps the next time we have a breaking-change we need to do for xmodel.
For now, we are leaving the name along and hard-coding this to return False in BaseStructure, and to return True in RemoteStructure.