Source code for emod_api.demographics.PropertiesAndAttributes

from emod_api.demographics.Updateable import Updateable
from typing import List

# TODO: most of the documentation in this file consists of stand-in stubs. Needs to be filled in.
#  https://github.com/InstituteforDiseaseModeling/emod-api/issues/695
[docs]class IndividualProperty(Updateable): def __init__(self, property: str, initial_distribution: List[float] = None, values: List[str] = None, transitions: List[float] = None, transmission_matrix: List[float] = None): """ https://docs.idmod.org/projects/emod-generic/en/latest/model-properties.html Args: initial_distribution: property: values: transitions: transmission_matrix: """ super().__init__() self.initial_distribution = initial_distribution self.property = property self.values = values self.transitions = transitions self.transmission_matrix = transmission_matrix
[docs] def to_dict(self) -> dict: individual_property = self.parameter_dict if self.initial_distribution is not None: individual_property.update({"Initial_Distribution": self.initial_distribution}) if self.property is not None: individual_property.update({"Property": self.property}) if self.values is not None: if self.property == "Age_Bin": individual_property.update({"Age_Bin_Edges_In_Years": self.values}) else: individual_property.update({"Values": self.values}) if self.transitions is not None: individual_property.update({"Transitions": self.transitions}) if self.transmission_matrix is not None: individual_property.update({"TransmissionMatrix": self.transmission_matrix}) return individual_property
[docs] @classmethod def from_dict(cls, ip_dict: dict) -> '__class__': available_args = ['initial_distribution', 'property', 'values', 'transitions', 'transmission_matrix'] args = {key: ip_dict[key] for key in available_args if key in ip_dict} return cls(**args)
def __eq__(self, other) -> bool: return self.to_dict() == other.to_dict()
[docs]class IndividualProperties(Updateable):
[docs] class DuplicateIndividualPropertyException(Exception): pass
[docs] class NoSuchIndividualPropertyException(Exception): pass
# TODO: this constructor call is WEIRD. it should take a list of IndividualProperties instead and remove # "if is None" checks on self.individual_properties (None should auto goes to [] in constructor) # https://github.com/InstituteforDiseaseModeling/emod-api/issues/685 def __init__(self, individual_property: IndividualProperty = None): """ https://docs.idmod.org/projects/emod-generic/en/latest/model-properties.html Args: individual_property: """ super().__init__() self.individual_properties = [individual_property] if individual_property else None
[docs] def add(self, individual_property: IndividualProperty, overwrite=False) -> None: if self.individual_properties is None: self.individual_properties = [] has_ip = self.has_individual_property(property_key=individual_property.property) if has_ip: if overwrite: # remove existing then add self.remove_individual_property(property_key=individual_property.property) else: msg = f"Property {individual_property.property} already present in IndividualProperties" raise self.DuplicateIndividualPropertyException(msg) self.individual_properties.append(individual_property)
[docs] def add_parameter(self, key, value): raise NotImplementedError("A parameter cannot be added to IndividualProperties.")
@property def ip_by_name(self): individual_properties = [] if self.individual_properties is None else self.individual_properties return {ip.property: ip for ip in individual_properties}
[docs] def has_individual_property(self, property_key: str) -> bool: return property_key in self.ip_by_name.keys()
[docs] def get_individual_property(self, property_key: str) -> IndividualProperty: ip = self.ip_by_name.get(property_key, None) if ip is None: msg = f"No IndividualProperty exists with the property key: {property_key}" raise self.NoSuchIndividualPropertyException(msg) return ip
[docs] def remove_individual_property(self, property_key: str): ips_to_keep = [ip for ip in self.individual_properties if ip.property != property_key] self.individual_properties = ips_to_keep
[docs] def to_dict(self) -> List[dict]: individual_properties = [] for ip in self.individual_properties: individual_properties.append(ip.to_dict()) return individual_properties
def __getitem__(self, index: int): return self.individual_properties[index] def __len__(self): if not self.individual_properties: return 0 return len(self.individual_properties)
[docs]class IndividualAttributes(Updateable):
[docs] class SusceptibilityDistribution(Updateable): def __init__(self, distribution_values: List[float] = None, result_scale_factor=None, result_values=None): """ https://docs.idmod.org/projects/emod-generic/en/latest/parameter-demographics.html#complex-distributions Args: distribution_values: result_scale_factor: result_values: """ super().__init__() self.distribution_values = distribution_values self.result_scale_factor = result_scale_factor self.result_values = result_values
[docs] def to_dict(self) -> dict: susceptibility_distribution = self.parameter_dict if self.distribution_values is not None: susceptibility_distribution.update({"DistributionValues": self.distribution_values}) if self.result_scale_factor is not None: susceptibility_distribution.update({"ResultScaleFactor": self.result_scale_factor}) if self.result_values is not None: susceptibility_distribution.update({"ResultValues": self.result_values}) return susceptibility_distribution
[docs] class AgeDistribution(Updateable): def __init__(self, distribution_values=None, result_scale_factor=None, result_values=None): """ https://docs.idmod.org/projects/emod-generic/en/latest/parameter-demographics.html#complex-distributions Args: distribution_values: result_scale_factor: result_values: """ super().__init__() self.distribution_values = distribution_values self.result_scale_factor = result_scale_factor self.result_values = result_values @property def num_dist_axes(self): import warnings warnings.warn(f"{__class__}: num_dist_axes (NumDistributionAxes) is not interpreted by EMOD and may be removed", DeprecationWarning, stacklevel=2) return self._num_dist_axes @num_dist_axes.setter def num_dist_axes(self, value): import warnings warnings.warn(f"{__class__}: num_dist_axes (NumDistributionAxes) is not interpreted by EMOD and may be removed", DeprecationWarning, stacklevel=2) self._num_dist_axes = value
[docs] def to_dict(self) -> dict: age_distribution = {} if self.distribution_values is not None: age_distribution.update({"DistributionValues": self.distribution_values}) if self.result_scale_factor is not None: age_distribution.update({"ResultScaleFactor": self.result_scale_factor}) if self.result_values is not None: age_distribution.update({"ResultValues": self.result_values}) return age_distribution
[docs] def from_dict(self, age_distribution: dict): if age_distribution is not None: self.distribution_values = age_distribution.get("DistributionValues") self.result_scale_factor = age_distribution.get("ResultScaleFactor") self.result_values = age_distribution.get("ResultValues") self._num_dist_axes = age_distribution.get("NumDistributionAxes") self.results_units = age_distribution.get("ResultUnits") return self
[docs] class FertilityDistribution(Updateable): def __init__(self, axis_names: List[str] = None, axis_scale_factors: List[float] = None, axis_units=None, num_distribution_axes=None, num_population_axes=None, num_population_groups=None, population_groups=None, result_scale_factor=None, result_units=None, result_values=None): """ https://docs.idmod.org/projects/emod-generic/en/latest/parameter-demographics.html#complex-distributions Args: axis_names: axis_scale_factors: axis_units: population_groups: result_scale_factor: result_units: result_values: """ super().__init__() self.axis_names = axis_names self.axis_scale_factors = axis_scale_factors self.axis_units = axis_units self._num_distribution_axes = num_distribution_axes self._num_population_axes = num_population_axes self._num_population_groups = num_population_groups self.population_groups = population_groups self.result_scale_factor = result_scale_factor self.result_units = result_units self.result_values = result_values @property def num_distribution_axes(self): import warnings warnings.warn(f"{__class__}: num_distribution_axes (NumDistributionAxes) is not interpreted by EMOD and may be removed", DeprecationWarning, stacklevel=2) return self._num_distribution_axes @num_distribution_axes.setter def num_distribution_axes(self, value): import warnings warnings.warn(f"{__class__}: num_distribution_axes (NumDistributionAxes) is not interpreted by EMOD and may be removed", DeprecationWarning, stacklevel=2) self._num_distribution_axes = value @property def num_population_axes(self): import warnings warnings.warn(f"{__class__}: num_population_axes (NumPopulationAxes) is not interpreted by EMOD and may be removed", DeprecationWarning, stacklevel=2) return self._num_population_axes @num_population_axes.setter def num_population_axes(self, value): import warnings warnings.warn(f"{__class__}: num_population_axes (NumPopulationAxes) is not interpreted by EMOD and may be removed", DeprecationWarning, stacklevel=2) self._num_population_axes = value @property def num_population_groups(self): import warnings warnings.warn(f"{__class__}: num_population_groups (NumPopulationGroups) is not interpreted by EMOD and may be removed", DeprecationWarning, stacklevel=2) return self._num_population_groups @num_population_groups.setter def num_population_groups(self, value): import warnings warnings.warn(f"{__class__}: num_population_groups (NumPopulationGroups) is not interpreted by EMOD and may be removed", DeprecationWarning, stacklevel=2) self._num_population_groups = value
[docs] def to_dict(self) -> dict: fertility_distribution = self.parameter_dict if self.axis_names is not None: fertility_distribution.update({"AxisNames": self.axis_names}) if self.axis_scale_factors is not None: fertility_distribution.update({"AxisScaleFactors": self.axis_scale_factors}) if self.axis_units is not None: fertility_distribution.update({"AxisUnits": self.axis_units}) if self._num_distribution_axes is not None: fertility_distribution.update({"NumDistributionAxes": self._num_distribution_axes}) if self._num_population_groups is not None: fertility_distribution.update({"NumPopulationGroups": self._num_population_groups}) if self.population_groups is not None: fertility_distribution.update({"PopulationGroups": self.population_groups}) if self.result_scale_factor is not None: fertility_distribution.update({"ResultScaleFactor": self.result_scale_factor}) if self.result_units is not None: fertility_distribution.update({"ResultUnits": self.result_units}) if self.result_values is not None: fertility_distribution.update({"ResultValues": self.result_values}) return fertility_distribution
[docs] def from_dict(self, fertility_distribution: dict): if fertility_distribution: self.axis_names = fertility_distribution.get("AxisNames") self.axis_scale_factors = fertility_distribution.get("AxisScaleFactors") self.axis_units = fertility_distribution.get("AxisUnits") self._num_distribution_axes = fertility_distribution.get("NumDistributionAxes") self._num_population_groups = fertility_distribution.get("NumPopulationGroups") self.population_groups = fertility_distribution.get("PopulationGroups") self.result_scale_factor = fertility_distribution.get("ResultScaleFactor") self.result_units = fertility_distribution.get("ResultUnits") self.result_values = fertility_distribution.get("ResultValues") return self
[docs] class MortalityDistribution(Updateable): def __init__(self, axis_names: List[str] = None, axis_scale_factors: List[float] = None, axis_units=None, num_distribution_axes=None, num_population_axes=None, num_population_groups=None, population_groups=None, result_scale_factor=None, result_units=None, result_values=None): """ https://docs.idmod.org/projects/emod-generic/en/latest/parameter-demographics.html#complex-distributions Args: axis_names: axis_scale_factors: axis_units: population_groups: result_scale_factor: result_units: result_values: """ super().__init__() self.axis_names = axis_names self.axis_scale_factors = axis_scale_factors self.axis_units = axis_units self._num_distribution_axes = num_distribution_axes self._num_population_axes = num_population_axes self._num_population_groups = num_population_groups self.population_groups = population_groups self.result_scale_factor = result_scale_factor self.result_units = result_units self.result_values = result_values @property def num_distribution_axes(self): import warnings warnings.warn( f"{__class__}: num_distribution_axes (NumDistributionAxes) is not interpreted by EMOD and may be removed", DeprecationWarning, stacklevel=2) return self._num_distribution_axes @num_distribution_axes.setter def num_distribution_axes(self, value): import warnings warnings.warn( f"{__class__}: num_distribution_axes (NumDistributionAxes) is not interpreted by EMOD and may be removed", DeprecationWarning, stacklevel=2) self._num_distribution_axes = value @property def num_population_axes(self): import warnings warnings.warn( f"{__class__}: num_population_axes (NumPopulationAxes) is not interpreted by EMOD and may be removed", DeprecationWarning, stacklevel=2) return self._num_population_axes @num_population_axes.setter def num_population_axes(self, value): import warnings warnings.warn( f"{__class__}: num_population_axes (NumPopulationAxes) is not interpreted by EMOD and may be removed", DeprecationWarning, stacklevel=2) self._num_population_axes = value @property def num_population_groups(self): import warnings warnings.warn( f"{__class__}: num_population_groups (NumPopulationGroups) is not interpreted by EMOD and may be removed", DeprecationWarning, stacklevel=2) return self._num_population_groups @num_population_groups.setter def num_population_groups(self, value): import warnings warnings.warn( f"{__class__}: num_population_groups (NumPopulationGroups) is not interpreted by EMOD and may be removed", DeprecationWarning, stacklevel=2) self._num_population_groups = value
[docs] def to_dict(self) -> dict: mortality_distribution = self.parameter_dict if self.axis_names is not None: mortality_distribution.update({"AxisNames": self.axis_names}) if self.axis_scale_factors is not None: mortality_distribution.update({"AxisScaleFactors": self.axis_scale_factors}) if self.axis_units is not None: mortality_distribution.update({"AxisUnits": self.axis_units}) if self._num_distribution_axes is not None: mortality_distribution.update({"NumDistributionAxes": self._num_distribution_axes}) if self._num_population_groups is not None: mortality_distribution.update({"NumPopulationGroups": self._num_population_groups}) if self.population_groups is not None: mortality_distribution.update({"PopulationGroups": self.population_groups}) if self.result_scale_factor is not None: mortality_distribution.update({"ResultScaleFactor": self.result_scale_factor}) if self.result_units is not None: mortality_distribution.update({"ResultUnits": self.result_units}) if self.result_values is not None: mortality_distribution.update({"ResultValues": self.result_values}) return mortality_distribution
[docs] def from_dict(self, mortality_distribution: dict): if mortality_distribution is None: return None self.axis_names = mortality_distribution.get("AxisNames") self.axis_scale_factors = mortality_distribution.get("AxisScaleFactors") self.axis_units = mortality_distribution.get("AxisUnits") self._num_distribution_axes = mortality_distribution.get("NumDistributionAxes") self._num_population_groups = mortality_distribution.get("NumPopulationGroups") self.population_groups = mortality_distribution.get("PopulationGroups") self.result_scale_factor = mortality_distribution.get("ResultScaleFactor") self.result_units = mortality_distribution.get("ResultUnits") self.result_values = mortality_distribution.get("ResultValues") return self
# TODO: this is the constructor for IndividualAttributes of all things. It really should be up with the class # definition. Also, the various classes under IndividualAttributes should be moved to top level in this file # or in other files. # https://github.com/InstituteforDiseaseModeling/emod-api/issues/694 def __init__(self, age_distribution_flag=None, age_distribution1=None, age_distribution2=None, age_distribution=None, prevalence_distribution_flag=None, prevalence_distribution1=None, prevalence_distribution2=None, immunity_distribution_flag=None, immunity_distribution1=None, immunity_distribution2=None, risk_distribution_flag=None, risk_distribution1=None, risk_distribution2=None, migration_heterogeneity_distribution_flag=None, migration_heterogeneity_distribution1=None, migration_heterogeneity_distribution2=None, fertility_distribution=None, mortality_distribution=None, mortality_distribution_male=None, mortality_distribution_female=None, susceptibility_distribution=None ): """ https://docs.idmod.org/projects/emod-generic/en/latest/parameter-demographics.html#individual-attributes Args: age_distribution_flag: age_distribution1: age_distribution2: age_distribution: prevalence_distribution_flag: prevalence_distribution1: prevalence_distribution2: immunity_distribution_flag: immunity_distribution1: immunity_distribution2: risk_distribution_flag: risk_distribution1: risk_distribution2: migration_heterogeneity_distribution_flag: migration_heterogeneity_distribution1: migration_heterogeneity_distribution2: fertility_distribution: mortality_distribution: mortality_distribution_male: mortality_distribution_female: susceptibility_distribution: """ super().__init__() self.age_distribution_flag = age_distribution_flag self.age_distribution1 = age_distribution1 self.age_distribution2 = age_distribution2 self.age_distribution = age_distribution self.prevalence_distribution_flag = prevalence_distribution_flag self.prevalence_distribution1 = prevalence_distribution1 self.prevalence_distribution2 = prevalence_distribution2 self.immunity_distribution_flag = immunity_distribution_flag self.immunity_distribution1 = immunity_distribution1 self.immunity_distribution2 = immunity_distribution2 self.risk_distribution_flag = risk_distribution_flag self.risk_distribution1 = risk_distribution1 self.risk_distribution2 = risk_distribution2 self.migration_heterogeneity_distribution_flag = migration_heterogeneity_distribution_flag self.migration_heterogeneity_distribution1 = migration_heterogeneity_distribution1 self.migration_heterogeneity_distribution2 = migration_heterogeneity_distribution2 self.fertility_distribution = fertility_distribution self.mortality_distribution = mortality_distribution self.mortality_distribution_male = mortality_distribution_male self.mortality_distribution_female = mortality_distribution_female self.susceptibility_distribution = susceptibility_distribution
[docs] def to_dict(self) -> dict: individual_attributes = self.parameter_dict if self.age_distribution_flag is not None: individual_attributes_age_distribution = { "AgeDistributionFlag": self.age_distribution_flag, "AgeDistribution1": self.age_distribution1, "AgeDistribution2": self.age_distribution2 } individual_attributes.update(individual_attributes_age_distribution) else: if self.age_distribution: individual_attributes_age_distribution = { "AgeDistribution": self.age_distribution.to_dict() } individual_attributes.update(individual_attributes_age_distribution) if self.prevalence_distribution_flag is not None: prevalence_distribution = { "PrevalenceDistributionFlag": self.prevalence_distribution_flag, "PrevalenceDistribution1": self.prevalence_distribution1, "PrevalenceDistribution2": self.prevalence_distribution2 } individual_attributes.update(prevalence_distribution) if self.immunity_distribution_flag is not None: immunity_distribution = { "ImmunityDistributionFlag": self.immunity_distribution_flag, "ImmunityDistribution1": self.immunity_distribution1, "ImmunityDistribution2": self.immunity_distribution2 } individual_attributes.update(immunity_distribution) if self.migration_heterogeneity_distribution_flag is not None: migration_heterogeneity_distribution = { "MigrationHeterogeneityDistributionFlag": self.migration_heterogeneity_distribution_flag, "MigrationHeterogeneityDistribution1": self.migration_heterogeneity_distribution1, "MigrationHeterogeneityDistribution2": self.migration_heterogeneity_distribution2 } individual_attributes.update(migration_heterogeneity_distribution) if self.risk_distribution_flag is not None: risk_distribution = { "RiskDistributionFlag": self.risk_distribution_flag, "RiskDistribution1": self.risk_distribution1, "RiskDistribution2": self.risk_distribution2 } individual_attributes.update(risk_distribution) if self.susceptibility_distribution is not None: individual_attributes.update({"SusceptibilityDistribution": self.susceptibility_distribution.to_dict()}) if self.fertility_distribution is not None: individual_attributes.update({"FertilityDistribution": self.fertility_distribution.to_dict()}) if self.mortality_distribution is not None: individual_attributes.update({"MortalityDistribution": self.mortality_distribution.to_dict()}) if self.mortality_distribution_male is not None: individual_attributes.update({"MortalityDistributionMale": self.mortality_distribution_male.to_dict()}) if self.mortality_distribution_female is not None: individual_attributes.update({"MortalityDistributionFemale": self.mortality_distribution_female.to_dict()}) return individual_attributes
[docs] def from_dict(self, individual_attributes: dict): self.age_distribution_flag = individual_attributes.get("AgeDistributionFlag") self.age_distribution1 = individual_attributes.get("AgeDistribution1") self.age_distribution2 = individual_attributes.get("AgeDistribution2") self.age_distribution = IndividualAttributes.AgeDistribution().from_dict( individual_attributes.get("AgeDistribution")) # DISTRIBUTION_COMPLEX self.prevalence_distribution_flag = individual_attributes.get("PrevalenceDistributionFlag") self.prevalence_distribution1 = individual_attributes.get("PrevalenceDistribution1") self.prevalence_distribution2 = individual_attributes.get("PrevalenceDistribution2") self.immunity_distribution_flag = individual_attributes.get("ImmunityDistributionFlag") self.immunity_distribution1 = individual_attributes.get("ImmunityDistribution1") self.immunity_distribution2 = individual_attributes.get("ImmunityDistribution2") self.risk_distribution_flag = individual_attributes.get("RiskDistributionFlag") self.risk_distribution1 = individual_attributes.get("RiskDistribution1") self.risk_distribution2 = individual_attributes.get("RiskDistribution2") self.migration_heterogeneity_distribution_flag = individual_attributes.get( "MigrationHeterogeneityDistributionFlag") self.migration_heterogeneity_distribution1 = individual_attributes.get("MigrationHeterogeneityDistribution1") self.migration_heterogeneity_distribution2 = individual_attributes.get("MigrationHeterogeneityDistribution2") self.fertility_distribution = IndividualAttributes.FertilityDistribution().from_dict( individual_attributes.get("FertilityDistribution")) self.mortality_distribution = IndividualAttributes.MortalityDistribution().from_dict( individual_attributes.get("MortalityDistribution")) self.mortality_distribution_male = IndividualAttributes.MortalityDistribution().from_dict( individual_attributes.get("MortalityDistributionMale")) self.mortality_distribution_female = IndividualAttributes.MortalityDistribution().from_dict( individual_attributes.get("MortalityDistributionFemale")) return self
[docs]class NodeAttributes(Updateable): def __init__(self, airport: int = None, altitude=None, area: float = None, birth_rate: float = None, country=None, growth_rate: float = None, name: str = None, latitude: float = None, longitude: float = None, metadata: dict = None, initial_population: int = None, region: int = None, seaport: int = None, larval_habitat_multiplier: List[float] = None, initial_vectors_per_species=None, infectivity_multiplier: float = None, extra_attributes: dict = None): """ https://docs.idmod.org/projects/emod-generic/en/latest/parameter-demographics.html#nodeattributes Args: airport: altitude: area: birth_rate: country: growth_rate: name: latitude: longitude: metadata: initial_population: region: seaport: larval_habitat_multiplier: initial_vectors_per_species: infectivity_multiplier: extra_attributes: """ super().__init__() self.airport = airport self.altitude = altitude self.area = area self.birth_rate = birth_rate self.country = country self.growth_rate = growth_rate self.initial_population = initial_population self.initial_vectors_per_species = initial_vectors_per_species self.larval_habitat_multiplier = larval_habitat_multiplier self.latitude = latitude self.longitude = longitude self.metadata = metadata self.name = name self.region = region self.seaport = seaport self.infectivity_multiplier = infectivity_multiplier self.extra_attributes = extra_attributes
[docs] def from_dict(self, node_attributes: dict): self.airport = node_attributes.get("Airport") self.altitude = node_attributes.get("Altitude") self.area = node_attributes.get("Area") self.country = node_attributes.get("country") self.growth_rate = node_attributes.get("GrowthRate") self.name = node_attributes.get("FacilityName") self.latitude = node_attributes.get("Latitude") self.longitude = node_attributes.get("Longitude") self.metadata = node_attributes.get("Metadata") self.initial_population = node_attributes.get("InitialPopulation") self.larval_habitat_multiplier = node_attributes.get("LarvalHabitatMultiplier") self.initial_vectors_per_species = node_attributes.get("InitialVectorsPerSpecies") self.birth_rate = node_attributes.get("BirthRate") self.seaport = node_attributes.get("Seaport") self.region = node_attributes.get("Region") self.infectivity_multiplier = node_attributes.get("InfectivityMultiplier") return self
[docs] def to_dict(self) -> dict: node_attributes = self.parameter_dict if self.birth_rate is not None: node_attributes.update({"BirthRate": self.birth_rate}) if self.area is not None: node_attributes.update({"Area": self.area}) if self.latitude is not None: node_attributes.update({"Latitude": self.latitude}) if self.longitude is not None: node_attributes.update({"Longitude": self.longitude}) if self.initial_population is not None: node_attributes.update({"InitialPopulation": int(self.initial_population)}) if self.name: node_attributes.update({"FacilityName": self.name}) if self.larval_habitat_multiplier is not None: node_attributes.update({"LarvalHabitatMultiplier": self.larval_habitat_multiplier}) if self.initial_vectors_per_species: node_attributes.update({"InitialVectorsPerSpecies": self.initial_vectors_per_species}) if self.airport is not None: node_attributes.update({"Airport": self.airport}) if self.altitude is not None: node_attributes.update({"Altitude": self.altitude}) if self.seaport is not None: node_attributes.update({"Seaport": self.seaport}) if self.region is not None: node_attributes.update({"Region": self.region}) if self.country is not None: node_attributes.update({"country": self.country}) if self.growth_rate is not None: node_attributes.update({"GrowthRate": self.growth_rate}) if self.metadata is not None: node_attributes.update({"Metadata": self.metadata}) if self.infectivity_multiplier is not None: node_attributes.update({"InfectivityMultiplier": self.infectivity_multiplier}) if self.extra_attributes is not None: node_attributes.update(self.extra_attributes) return node_attributes