OpenFisca-Core-test 46.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openfisca_core/__init__.py +0 -0
- openfisca_core/commons/__init__.py +18 -0
- openfisca_core/commons/dummy.py +25 -0
- openfisca_core/commons/formulas.py +121 -0
- openfisca_core/commons/misc.py +109 -0
- openfisca_core/commons/py.typed +0 -0
- openfisca_core/commons/rates.py +114 -0
- openfisca_core/commons/tests/__init__.py +0 -0
- openfisca_core/commons/tests/test_dummy.py +9 -0
- openfisca_core/commons/tests/test_formulas.py +75 -0
- openfisca_core/commons/tests/test_rates.py +26 -0
- openfisca_core/data_storage/__init__.py +7 -0
- openfisca_core/data_storage/in_memory_storage.py +198 -0
- openfisca_core/data_storage/on_disk_storage.py +307 -0
- openfisca_core/data_storage/types.py +14 -0
- openfisca_core/entities/__init__.py +23 -0
- openfisca_core/entities/_core_entity.py +217 -0
- openfisca_core/entities/_description.py +55 -0
- openfisca_core/entities/entity.py +60 -0
- openfisca_core/entities/group_entity.py +123 -0
- openfisca_core/entities/helpers.py +165 -0
- openfisca_core/entities/py.typed +0 -0
- openfisca_core/entities/role.py +92 -0
- openfisca_core/entities/tests/__init__.py +0 -0
- openfisca_core/entities/tests/test_entity.py +10 -0
- openfisca_core/entities/tests/test_group_entity.py +70 -0
- openfisca_core/entities/tests/test_role.py +11 -0
- openfisca_core/entities/types.py +44 -0
- openfisca_core/errors/__init__.py +64 -0
- openfisca_core/errors/cycle_error.py +2 -0
- openfisca_core/errors/empty_argument_error.py +39 -0
- openfisca_core/errors/nan_creation_error.py +2 -0
- openfisca_core/errors/parameter_not_found_error.py +16 -0
- openfisca_core/errors/parameter_parsing_error.py +18 -0
- openfisca_core/errors/period_mismatch_error.py +9 -0
- openfisca_core/errors/situation_parsing_error.py +26 -0
- openfisca_core/errors/spiral_error.py +2 -0
- openfisca_core/errors/variable_name_config_error.py +2 -0
- openfisca_core/errors/variable_not_found_error.py +29 -0
- openfisca_core/experimental/__init__.py +9 -0
- openfisca_core/experimental/_errors.py +5 -0
- openfisca_core/experimental/_memory_config.py +41 -0
- openfisca_core/formula_helpers.py +9 -0
- openfisca_core/holders/__init__.py +33 -0
- openfisca_core/holders/helpers.py +93 -0
- openfisca_core/holders/holder.py +328 -0
- openfisca_core/holders/tests/__init__.py +0 -0
- openfisca_core/holders/tests/test_helpers.py +134 -0
- openfisca_core/holders/types.py +3 -0
- openfisca_core/indexed_enums/__init__.py +18 -0
- openfisca_core/indexed_enums/_enum_type.py +79 -0
- openfisca_core/indexed_enums/_errors.py +35 -0
- openfisca_core/indexed_enums/_guards.py +209 -0
- openfisca_core/indexed_enums/_utils.py +180 -0
- openfisca_core/indexed_enums/config.py +6 -0
- openfisca_core/indexed_enums/enum.py +218 -0
- openfisca_core/indexed_enums/enum_array.py +316 -0
- openfisca_core/indexed_enums/py.typed +0 -0
- openfisca_core/indexed_enums/tests/__init__.py +0 -0
- openfisca_core/indexed_enums/tests/test_enum.py +135 -0
- openfisca_core/indexed_enums/tests/test_enum_array.py +30 -0
- openfisca_core/indexed_enums/types.py +49 -0
- openfisca_core/memory_config.py +9 -0
- openfisca_core/model_api.py +71 -0
- openfisca_core/parameters/__init__.py +71 -0
- openfisca_core/parameters/at_instant_like.py +17 -0
- openfisca_core/parameters/config.py +55 -0
- openfisca_core/parameters/helpers.py +106 -0
- openfisca_core/parameters/parameter.py +217 -0
- openfisca_core/parameters/parameter_at_instant.py +75 -0
- openfisca_core/parameters/parameter_node.py +176 -0
- openfisca_core/parameters/parameter_node_at_instant.py +57 -0
- openfisca_core/parameters/parameter_scale.py +121 -0
- openfisca_core/parameters/parameter_scale_bracket.py +7 -0
- openfisca_core/parameters/values_history.py +5 -0
- openfisca_core/parameters/vectorial_parameter_node_at_instant.py +179 -0
- openfisca_core/periods/__init__.py +77 -0
- openfisca_core/periods/_errors.py +28 -0
- openfisca_core/periods/_parsers.py +122 -0
- openfisca_core/periods/config.py +20 -0
- openfisca_core/periods/date_unit.py +110 -0
- openfisca_core/periods/helpers.py +314 -0
- openfisca_core/periods/instant_.py +226 -0
- openfisca_core/periods/period_.py +919 -0
- openfisca_core/periods/py.typed +0 -0
- openfisca_core/periods/tests/__init__.py +0 -0
- openfisca_core/periods/tests/helpers/__init__.py +0 -0
- openfisca_core/periods/tests/helpers/test_helpers.py +65 -0
- openfisca_core/periods/tests/helpers/test_instant.py +73 -0
- openfisca_core/periods/tests/helpers/test_period.py +134 -0
- openfisca_core/periods/tests/test_instant.py +32 -0
- openfisca_core/periods/tests/test_parsers.py +129 -0
- openfisca_core/periods/tests/test_period.py +283 -0
- openfisca_core/populations/__init__.py +64 -0
- openfisca_core/populations/_core_population.py +454 -0
- openfisca_core/populations/_errors.py +65 -0
- openfisca_core/populations/group_population.py +326 -0
- openfisca_core/populations/population.py +142 -0
- openfisca_core/populations/tests/__init__.py +0 -0
- openfisca_core/populations/tests/test_members_position.py +116 -0
- openfisca_core/populations/types.py +110 -0
- openfisca_core/projectors/__init__.py +39 -0
- openfisca_core/projectors/entity_to_person_projector.py +12 -0
- openfisca_core/projectors/first_person_to_entity_projector.py +13 -0
- openfisca_core/projectors/helpers.py +139 -0
- openfisca_core/projectors/projector.py +38 -0
- openfisca_core/projectors/typing.py +27 -0
- openfisca_core/projectors/unique_role_to_entity_projector.py +14 -0
- openfisca_core/rates.py +9 -0
- openfisca_core/reforms/__init__.py +24 -0
- openfisca_core/reforms/in_yaml_test_reform.py +28 -0
- openfisca_core/reforms/reform.py +86 -0
- openfisca_core/scripts/__init__.py +106 -0
- openfisca_core/scripts/__pycache__/__init__.cpython-310-pytest-8.3.3.pyc +0 -0
- openfisca_core/scripts/__pycache__/__init__.cpython-310-pytest-8.4.2.pyc +0 -0
- openfisca_core/scripts/__pycache__/__init__.cpython-310.pyc +0 -0
- openfisca_core/scripts/__pycache__/__init__.cpython-311-pytest-8.3.3.pyc +0 -0
- openfisca_core/scripts/__pycache__/__init__.cpython-311-pytest-8.4.2.pyc +0 -0
- openfisca_core/scripts/__pycache__/__init__.cpython-311.pyc +0 -0
- openfisca_core/scripts/__pycache__/__init__.cpython-312-pytest-8.4.2.pyc +0 -0
- openfisca_core/scripts/__pycache__/__init__.cpython-312.pyc +0 -0
- openfisca_core/scripts/__pycache__/__init__.cpython-313-pytest-8.3.3.pyc +0 -0
- openfisca_core/scripts/__pycache__/__init__.cpython-313-pytest-8.4.2.pyc +0 -0
- openfisca_core/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
- openfisca_core/scripts/__pycache__/find_placeholders.cpython-311.pyc +0 -0
- openfisca_core/scripts/__pycache__/find_placeholders.cpython-313.pyc +0 -0
- openfisca_core/scripts/__pycache__/measure_numpy_condition_notations.cpython-311.pyc +0 -0
- openfisca_core/scripts/__pycache__/measure_numpy_condition_notations.cpython-313.pyc +0 -0
- openfisca_core/scripts/__pycache__/measure_performances.cpython-311.pyc +0 -0
- openfisca_core/scripts/__pycache__/measure_performances.cpython-313.pyc +0 -0
- openfisca_core/scripts/__pycache__/measure_performances_fancy_indexing.cpython-311.pyc +0 -0
- openfisca_core/scripts/__pycache__/measure_performances_fancy_indexing.cpython-313.pyc +0 -0
- openfisca_core/scripts/__pycache__/openfisca_command.cpython-310.pyc +0 -0
- openfisca_core/scripts/__pycache__/openfisca_command.cpython-311.pyc +0 -0
- openfisca_core/scripts/__pycache__/openfisca_command.cpython-312.pyc +0 -0
- openfisca_core/scripts/__pycache__/openfisca_command.cpython-313.pyc +0 -0
- openfisca_core/scripts/__pycache__/remove_fuzzy.cpython-311.pyc +0 -0
- openfisca_core/scripts/__pycache__/remove_fuzzy.cpython-313.pyc +0 -0
- openfisca_core/scripts/__pycache__/run_test.cpython-310.pyc +0 -0
- openfisca_core/scripts/__pycache__/run_test.cpython-311.pyc +0 -0
- openfisca_core/scripts/__pycache__/run_test.cpython-312.pyc +0 -0
- openfisca_core/scripts/__pycache__/run_test.cpython-313.pyc +0 -0
- openfisca_core/scripts/__pycache__/simulation_generator.cpython-311.pyc +0 -0
- openfisca_core/scripts/__pycache__/simulation_generator.cpython-313.pyc +0 -0
- openfisca_core/scripts/assets/__init__.py +0 -0
- openfisca_core/scripts/assets/__pycache__/__init__.cpython-311.pyc +0 -0
- openfisca_core/scripts/assets/__pycache__/__init__.cpython-313.pyc +0 -0
- openfisca_core/scripts/assets/index.html +140 -0
- openfisca_core/scripts/find_placeholders.py +51 -0
- openfisca_core/scripts/measure_numpy_condition_notations.py +137 -0
- openfisca_core/scripts/measure_performances.py +291 -0
- openfisca_core/scripts/measure_performances_fancy_indexing.py +89 -0
- openfisca_core/scripts/migrations/__init__.py +0 -0
- openfisca_core/scripts/migrations/__pycache__/__init__.cpython-311.pyc +0 -0
- openfisca_core/scripts/migrations/__pycache__/__init__.cpython-313.pyc +0 -0
- openfisca_core/scripts/migrations/__pycache__/v24_to_25.cpython-311.pyc +0 -0
- openfisca_core/scripts/migrations/__pycache__/v24_to_25.cpython-313.pyc +0 -0
- openfisca_core/scripts/migrations/v16_2_to_v17/__init__.py +0 -0
- openfisca_core/scripts/migrations/v16_2_to_v17/__pycache__/__init__.cpython-311.pyc +0 -0
- openfisca_core/scripts/migrations/v16_2_to_v17/__pycache__/__init__.cpython-313.pyc +0 -0
- openfisca_core/scripts/migrations/v16_2_to_v17/__pycache__/xml_to_yaml_country_template.cpython-311.pyc +0 -0
- openfisca_core/scripts/migrations/v16_2_to_v17/__pycache__/xml_to_yaml_country_template.cpython-313.pyc +0 -0
- openfisca_core/scripts/migrations/v16_2_to_v17/__pycache__/xml_to_yaml_extension_template.cpython-311.pyc +0 -0
- openfisca_core/scripts/migrations/v16_2_to_v17/__pycache__/xml_to_yaml_extension_template.cpython-313.pyc +0 -0
- openfisca_core/scripts/migrations/v16_2_to_v17/legislation.xsd +102 -0
- openfisca_core/scripts/migrations/v16_2_to_v17/xml_to_yaml_country_template.py +30 -0
- openfisca_core/scripts/migrations/v16_2_to_v17/xml_to_yaml_extension_template.py +26 -0
- openfisca_core/scripts/migrations/v24_to_25.py +156 -0
- openfisca_core/scripts/openfisca_command.py +192 -0
- openfisca_core/scripts/remove_fuzzy.py +213 -0
- openfisca_core/scripts/run_test.py +51 -0
- openfisca_core/scripts/simulation_generator.py +90 -0
- openfisca_core/simulation_builder.py +16 -0
- openfisca_core/simulations/__init__.py +45 -0
- openfisca_core/simulations/_build_default_simulation.py +158 -0
- openfisca_core/simulations/_build_from_variables.py +230 -0
- openfisca_core/simulations/_type_guards.py +298 -0
- openfisca_core/simulations/helpers.py +111 -0
- openfisca_core/simulations/simulation.py +618 -0
- openfisca_core/simulations/simulation_builder.py +855 -0
- openfisca_core/simulations/typing.py +203 -0
- openfisca_core/taxbenefitsystems/__init__.py +26 -0
- openfisca_core/taxbenefitsystems/tax_benefit_system.py +584 -0
- openfisca_core/taxscales/__init__.py +35 -0
- openfisca_core/taxscales/abstract_rate_tax_scale.py +41 -0
- openfisca_core/taxscales/abstract_tax_scale.py +53 -0
- openfisca_core/taxscales/amount_tax_scale_like.py +55 -0
- openfisca_core/taxscales/helpers.py +44 -0
- openfisca_core/taxscales/linear_average_rate_tax_scale.py +76 -0
- openfisca_core/taxscales/marginal_amount_tax_scale.py +34 -0
- openfisca_core/taxscales/marginal_rate_tax_scale.py +286 -0
- openfisca_core/taxscales/rate_tax_scale_like.py +214 -0
- openfisca_core/taxscales/single_amount_tax_scale.py +32 -0
- openfisca_core/taxscales/tax_scale_like.py +65 -0
- openfisca_core/tools/__init__.py +94 -0
- openfisca_core/tools/parallel_plugin.py +55 -0
- openfisca_core/tools/simulation_dumper.py +137 -0
- openfisca_core/tools/test_runner.py +802 -0
- openfisca_core/tracers/__init__.py +40 -0
- openfisca_core/tracers/computation_log.py +196 -0
- openfisca_core/tracers/flat_trace.py +87 -0
- openfisca_core/tracers/full_tracer.py +172 -0
- openfisca_core/tracers/performance_log.py +133 -0
- openfisca_core/tracers/simple_tracer.py +64 -0
- openfisca_core/tracers/trace_node.py +121 -0
- openfisca_core/tracers/tracing_parameter_node_at_instant.py +81 -0
- openfisca_core/types.py +548 -0
- openfisca_core/variables/__init__.py +26 -0
- openfisca_core/variables/config.py +53 -0
- openfisca_core/variables/helpers.py +67 -0
- openfisca_core/variables/tests/__init__.py +0 -0
- openfisca_core/variables/tests/test_definition_period.py +43 -0
- openfisca_core/variables/variable.py +474 -0
- openfisca_core/warnings/__init__.py +25 -0
- openfisca_core/warnings/libyaml_warning.py +2 -0
- openfisca_core/warnings/tempfile_warning.py +2 -0
- openfisca_core_test-46.0.0.data/data/share/openfisca/openfisca-core/CHANGELOG.md +4209 -0
- openfisca_core_test-46.0.0.data/data/share/openfisca/openfisca-core/README.md +152 -0
- openfisca_core_test-46.0.0.dist-info/METADATA +194 -0
- openfisca_core_test-46.0.0.dist-info/RECORD +237 -0
- openfisca_core_test-46.0.0.dist-info/WHEEL +5 -0
- openfisca_core_test-46.0.0.dist-info/entry_points.txt +3 -0
- openfisca_core_test-46.0.0.dist-info/licenses/LICENSE +661 -0
- openfisca_core_test-46.0.0.dist-info/top_level.txt +2 -0
- openfisca_web_api/__init__.py +0 -0
- openfisca_web_api/app.py +226 -0
- openfisca_web_api/errors.py +11 -0
- openfisca_web_api/handlers.py +90 -0
- openfisca_web_api/loader/__init__.py +22 -0
- openfisca_web_api/loader/entities.py +28 -0
- openfisca_web_api/loader/parameters.py +122 -0
- openfisca_web_api/loader/spec.py +168 -0
- openfisca_web_api/loader/tax_benefit_system.py +27 -0
- openfisca_web_api/loader/variables.py +121 -0
- openfisca_web_api/openAPI.yml +434 -0
- openfisca_web_api/scripts/__init__.py +0 -0
- openfisca_web_api/scripts/serve.py +99 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import warnings
|
|
5
|
+
from collections.abc import Sequence
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import numpy
|
|
9
|
+
import psutil
|
|
10
|
+
|
|
11
|
+
from openfisca_core import (
|
|
12
|
+
commons,
|
|
13
|
+
errors,
|
|
14
|
+
periods,
|
|
15
|
+
types,
|
|
16
|
+
)
|
|
17
|
+
from openfisca_core import (
|
|
18
|
+
data_storage as storage,
|
|
19
|
+
)
|
|
20
|
+
from openfisca_core import (
|
|
21
|
+
indexed_enums as enums,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
from . import types as t
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Holder:
|
|
28
|
+
"""A holder keeps tracks of a variable values after they have been calculated, or set as an input."""
|
|
29
|
+
|
|
30
|
+
def __init__(self, variable, population) -> None:
|
|
31
|
+
self.population = population
|
|
32
|
+
self.variable = variable
|
|
33
|
+
self.simulation = population.simulation
|
|
34
|
+
self._eternal = self.variable.definition_period == periods.DateUnit.ETERNITY
|
|
35
|
+
self._memory_storage = storage.InMemoryStorage(is_eternal=self._eternal)
|
|
36
|
+
|
|
37
|
+
# By default, do not activate on-disk storage, or variable dropping
|
|
38
|
+
self._disk_storage = None
|
|
39
|
+
self._on_disk_storable = False
|
|
40
|
+
self._do_not_store = False
|
|
41
|
+
if self.simulation and self.simulation.memory_config:
|
|
42
|
+
if (
|
|
43
|
+
self.variable.name
|
|
44
|
+
not in self.simulation.memory_config.priority_variables
|
|
45
|
+
):
|
|
46
|
+
self._disk_storage = self.create_disk_storage()
|
|
47
|
+
self._on_disk_storable = True
|
|
48
|
+
if self.variable.name in self.simulation.memory_config.variables_to_drop:
|
|
49
|
+
self._do_not_store = True
|
|
50
|
+
|
|
51
|
+
def clone(self, population: t.CorePopulation) -> t.Holder:
|
|
52
|
+
"""Copy the holder just enough to be able to run a new simulation without modifying the original simulation."""
|
|
53
|
+
new = commons.empty_clone(self)
|
|
54
|
+
new_dict = new.__dict__
|
|
55
|
+
|
|
56
|
+
for key, value in self.__dict__.items():
|
|
57
|
+
if key not in ("population", "formula", "simulation"):
|
|
58
|
+
new_dict[key] = value
|
|
59
|
+
|
|
60
|
+
new_dict["population"] = population
|
|
61
|
+
new_dict["simulation"] = population.simulation
|
|
62
|
+
|
|
63
|
+
return new
|
|
64
|
+
|
|
65
|
+
def create_disk_storage(self, directory=None, preserve=False):
|
|
66
|
+
if directory is None:
|
|
67
|
+
directory = self.simulation.data_storage_dir
|
|
68
|
+
storage_dir = os.path.join(directory, self.variable.name)
|
|
69
|
+
if not os.path.isdir(storage_dir):
|
|
70
|
+
os.mkdir(storage_dir)
|
|
71
|
+
return storage.OnDiskStorage(
|
|
72
|
+
storage_dir,
|
|
73
|
+
self._eternal,
|
|
74
|
+
preserve_storage_dir=preserve,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
def delete_arrays(self, period=None) -> None:
|
|
78
|
+
"""If ``period`` is ``None``, remove all known values of the variable.
|
|
79
|
+
|
|
80
|
+
If ``period`` is not ``None``, only remove all values for any period included in period (e.g. if period is "2017", values for "2017-01", "2017-07", etc. would be removed)
|
|
81
|
+
"""
|
|
82
|
+
self._memory_storage.delete(period)
|
|
83
|
+
if self._disk_storage:
|
|
84
|
+
self._disk_storage.delete(period)
|
|
85
|
+
|
|
86
|
+
def get_array(self, period):
|
|
87
|
+
"""Get the value of the variable for the given period.
|
|
88
|
+
|
|
89
|
+
If the value is not known, return ``None``.
|
|
90
|
+
"""
|
|
91
|
+
if self.variable.is_neutralized:
|
|
92
|
+
return self.default_array()
|
|
93
|
+
value = self._memory_storage.get(period)
|
|
94
|
+
if value is not None:
|
|
95
|
+
return value
|
|
96
|
+
if self._disk_storage:
|
|
97
|
+
return self._disk_storage.get(period)
|
|
98
|
+
return None
|
|
99
|
+
|
|
100
|
+
def get_memory_usage(self) -> t.MemoryUsage:
|
|
101
|
+
"""Get data about the virtual memory usage of the Holder.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
Memory usage data.
|
|
105
|
+
|
|
106
|
+
Examples:
|
|
107
|
+
>>> from pprint import pprint
|
|
108
|
+
|
|
109
|
+
>>> from openfisca_core import (
|
|
110
|
+
... entities,
|
|
111
|
+
... populations,
|
|
112
|
+
... simulations,
|
|
113
|
+
... taxbenefitsystems,
|
|
114
|
+
... variables,
|
|
115
|
+
... )
|
|
116
|
+
|
|
117
|
+
>>> entity = entities.Entity("", "", "", "")
|
|
118
|
+
|
|
119
|
+
>>> class MyVariable(variables.Variable):
|
|
120
|
+
... definition_period = periods.DateUnit.YEAR
|
|
121
|
+
... entity = entity
|
|
122
|
+
... value_type = int
|
|
123
|
+
|
|
124
|
+
>>> population = populations.Population(entity)
|
|
125
|
+
>>> variable = MyVariable()
|
|
126
|
+
>>> holder = Holder(variable, population)
|
|
127
|
+
|
|
128
|
+
>>> tbs = taxbenefitsystems.TaxBenefitSystem([entity])
|
|
129
|
+
>>> entities = {entity.key: population}
|
|
130
|
+
>>> simulation = simulations.Simulation(tbs, entities)
|
|
131
|
+
>>> holder.simulation = simulation
|
|
132
|
+
|
|
133
|
+
>>> pprint(holder.get_memory_usage(), indent=3)
|
|
134
|
+
{ 'cell_size': nan,
|
|
135
|
+
'dtype': <class 'numpy.int32'>,
|
|
136
|
+
'nb_arrays': 0,
|
|
137
|
+
'nb_cells_by_array': 0,
|
|
138
|
+
'total_nb_bytes': 0...
|
|
139
|
+
|
|
140
|
+
"""
|
|
141
|
+
usage = t.MemoryUsage(
|
|
142
|
+
nb_cells_by_array=self.population.count,
|
|
143
|
+
dtype=self.variable.dtype,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
usage.update(self._memory_storage.get_memory_usage())
|
|
147
|
+
|
|
148
|
+
if self.simulation.trace:
|
|
149
|
+
nb_requests = self.simulation.tracer.get_nb_requests(self.variable.name)
|
|
150
|
+
usage.update(
|
|
151
|
+
{
|
|
152
|
+
"nb_requests": nb_requests,
|
|
153
|
+
"nb_requests_by_array": (
|
|
154
|
+
nb_requests / float(usage["nb_arrays"])
|
|
155
|
+
if usage["nb_arrays"] > 0
|
|
156
|
+
else numpy.nan
|
|
157
|
+
),
|
|
158
|
+
},
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
return usage
|
|
162
|
+
|
|
163
|
+
def get_known_periods(self):
|
|
164
|
+
"""Get the list of periods the variable value is known for."""
|
|
165
|
+
return list(self._memory_storage.get_known_periods()) + list(
|
|
166
|
+
self._disk_storage.get_known_periods() if self._disk_storage else [],
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
def set_input(
|
|
170
|
+
self,
|
|
171
|
+
period: types.Period,
|
|
172
|
+
array: numpy.ndarray | Sequence[Any],
|
|
173
|
+
) -> numpy.ndarray | None:
|
|
174
|
+
"""Set a Variable's array of values of a given Period.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
period: The period at which the value is set.
|
|
178
|
+
array: The input value for the variable.
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
The set input array.
|
|
182
|
+
|
|
183
|
+
Note:
|
|
184
|
+
If a ``set_input`` property has been set for the variable, this
|
|
185
|
+
method may accept inputs for periods not matching the
|
|
186
|
+
``definition_period`` of the Variable. To read
|
|
187
|
+
more about this, check the `documentation`_.
|
|
188
|
+
|
|
189
|
+
Examples:
|
|
190
|
+
>>> from openfisca_core import entities, populations, variables
|
|
191
|
+
|
|
192
|
+
>>> entity = entities.Entity("", "", "", "")
|
|
193
|
+
|
|
194
|
+
>>> class MyVariable(variables.Variable):
|
|
195
|
+
... definition_period = periods.DateUnit.YEAR
|
|
196
|
+
... entity = entity
|
|
197
|
+
... value_type = float
|
|
198
|
+
|
|
199
|
+
>>> variable = MyVariable()
|
|
200
|
+
|
|
201
|
+
>>> population = populations.Population(entity)
|
|
202
|
+
>>> population.count = 2
|
|
203
|
+
|
|
204
|
+
>>> holder = Holder(variable, population)
|
|
205
|
+
>>> holder.set_input("2018", numpy.array([12.5, 14]))
|
|
206
|
+
>>> holder.get_array("2018")
|
|
207
|
+
array([12.5, 14. ], dtype=float32)
|
|
208
|
+
|
|
209
|
+
>>> holder.set_input("2018", [12.5, 14])
|
|
210
|
+
>>> holder.get_array("2018")
|
|
211
|
+
array([12.5, 14. ], dtype=float32)
|
|
212
|
+
|
|
213
|
+
.. _documentation:
|
|
214
|
+
https://openfisca.org/doc/coding-the-legislation/35_periods.html#set-input-automatically-process-variable-inputs-defined-for-periods-not-matching-the-definition-period
|
|
215
|
+
|
|
216
|
+
"""
|
|
217
|
+
period = periods.period(period)
|
|
218
|
+
|
|
219
|
+
if period.unit == periods.DateUnit.ETERNITY and not self._eternal:
|
|
220
|
+
error_message = os.linesep.join(
|
|
221
|
+
[
|
|
222
|
+
"Unable to set a value for variable {1} for {0}.",
|
|
223
|
+
"{1} is only defined for {2}s. Please adapt your input.",
|
|
224
|
+
],
|
|
225
|
+
).format(
|
|
226
|
+
periods.DateUnit.ETERNITY.upper(),
|
|
227
|
+
self.variable.name,
|
|
228
|
+
self.variable.definition_period,
|
|
229
|
+
)
|
|
230
|
+
raise errors.PeriodMismatchError(
|
|
231
|
+
self.variable.name,
|
|
232
|
+
period,
|
|
233
|
+
self.variable.definition_period,
|
|
234
|
+
error_message,
|
|
235
|
+
)
|
|
236
|
+
if self.variable.is_neutralized:
|
|
237
|
+
warning_message = f"You cannot set a value for the variable {self.variable.name}, as it has been neutralized. The value you provided ({array}) will be ignored."
|
|
238
|
+
return warnings.warn(warning_message, Warning, stacklevel=2)
|
|
239
|
+
if self.variable.value_type in (float, int) and isinstance(array, str):
|
|
240
|
+
array = commons.eval_expression(array)
|
|
241
|
+
if self.variable.set_input:
|
|
242
|
+
return self.variable.set_input(self, period, array)
|
|
243
|
+
return self._set(period, array)
|
|
244
|
+
|
|
245
|
+
def _to_array(self, value):
|
|
246
|
+
if not isinstance(value, numpy.ndarray):
|
|
247
|
+
value = numpy.asarray(value)
|
|
248
|
+
if value.ndim == 0:
|
|
249
|
+
# 0-dim arrays are casted to scalar when they interact with float. We don't want that.
|
|
250
|
+
value = value.reshape(1)
|
|
251
|
+
if len(value) != self.population.count:
|
|
252
|
+
msg = f'Unable to set value "{value}" for variable "{self.variable.name}", as its length is {len(value)} while there are {self.population.count} {self.population.entity.plural} in the simulation.'
|
|
253
|
+
raise ValueError(
|
|
254
|
+
msg,
|
|
255
|
+
)
|
|
256
|
+
if self.variable.value_type == enums.Enum:
|
|
257
|
+
value = self.variable.possible_values.encode(value)
|
|
258
|
+
if value.dtype != self.variable.dtype:
|
|
259
|
+
try:
|
|
260
|
+
value = value.astype(self.variable.dtype)
|
|
261
|
+
except ValueError:
|
|
262
|
+
msg = f'Unable to set value "{value}" for variable "{self.variable.name}", as the variable dtype "{self.variable.dtype}" does not match the value dtype "{value.dtype}".'
|
|
263
|
+
raise ValueError(
|
|
264
|
+
msg,
|
|
265
|
+
)
|
|
266
|
+
return value
|
|
267
|
+
|
|
268
|
+
def _set(self, period, value) -> None:
|
|
269
|
+
value = self._to_array(value)
|
|
270
|
+
if not self._eternal:
|
|
271
|
+
if period is None:
|
|
272
|
+
msg = (
|
|
273
|
+
f"A period must be specified to set values, except for variables with "
|
|
274
|
+
f"{periods.DateUnit.ETERNITY.upper()} as as period_definition."
|
|
275
|
+
)
|
|
276
|
+
raise ValueError(
|
|
277
|
+
msg,
|
|
278
|
+
)
|
|
279
|
+
if self.variable.definition_period != period.unit or period.size > 1:
|
|
280
|
+
name = self.variable.name
|
|
281
|
+
period_size_adj = (
|
|
282
|
+
f"{period.unit}"
|
|
283
|
+
if (period.size == 1)
|
|
284
|
+
else f"{period.size}-{period.unit}s"
|
|
285
|
+
)
|
|
286
|
+
error_message = os.linesep.join(
|
|
287
|
+
[
|
|
288
|
+
f'Unable to set a value for variable "{name}" for {period_size_adj}-long period "{period}".',
|
|
289
|
+
f'"{name}" can only be set for one {self.variable.definition_period} at a time. Please adapt your input.',
|
|
290
|
+
f'If you are the maintainer of "{name}", you can consider adding it a set_input attribute to enable automatic period casting.',
|
|
291
|
+
],
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
raise errors.PeriodMismatchError(
|
|
295
|
+
self.variable.name,
|
|
296
|
+
period,
|
|
297
|
+
self.variable.definition_period,
|
|
298
|
+
error_message,
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
should_store_on_disk = (
|
|
302
|
+
self._on_disk_storable
|
|
303
|
+
and self._memory_storage.get(period) is None
|
|
304
|
+
and psutil.virtual_memory().percent # If there is already a value in memory, replace it and don't put a new value in the disk storage
|
|
305
|
+
>= self.simulation.memory_config.max_memory_occupation_pc
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
if should_store_on_disk:
|
|
309
|
+
self._disk_storage.put(value, period)
|
|
310
|
+
else:
|
|
311
|
+
self._memory_storage.put(value, period)
|
|
312
|
+
|
|
313
|
+
def put_in_cache(self, value, period) -> None:
|
|
314
|
+
if self._do_not_store:
|
|
315
|
+
return
|
|
316
|
+
|
|
317
|
+
if (
|
|
318
|
+
self.simulation.opt_out_cache
|
|
319
|
+
and self.simulation.tax_benefit_system.cache_blacklist
|
|
320
|
+
and self.variable.name in self.simulation.tax_benefit_system.cache_blacklist
|
|
321
|
+
):
|
|
322
|
+
return
|
|
323
|
+
|
|
324
|
+
self._set(period, value)
|
|
325
|
+
|
|
326
|
+
def default_array(self):
|
|
327
|
+
"""Return a new array of the appropriate length for the entity, filled with the variable default values."""
|
|
328
|
+
return self.variable.default_array(self.population.count)
|
|
File without changes
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from openfisca_core import holders, tools
|
|
4
|
+
from openfisca_core.entities import Entity
|
|
5
|
+
from openfisca_core.holders import Holder
|
|
6
|
+
from openfisca_core.periods import DateUnit, Instant, Period
|
|
7
|
+
from openfisca_core.populations import Population
|
|
8
|
+
from openfisca_core.variables import Variable
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.fixture
|
|
12
|
+
def people():
|
|
13
|
+
return Entity(
|
|
14
|
+
key="person",
|
|
15
|
+
plural="people",
|
|
16
|
+
label="An individual member of a larger group.",
|
|
17
|
+
doc="People have the particularity of not being someone else.",
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@pytest.fixture
|
|
22
|
+
def Income(people):
|
|
23
|
+
return type(
|
|
24
|
+
"Income",
|
|
25
|
+
(Variable,),
|
|
26
|
+
{"value_type": float, "entity": people},
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@pytest.fixture
|
|
31
|
+
def population(people):
|
|
32
|
+
population = Population(people)
|
|
33
|
+
population.count = 1
|
|
34
|
+
return population
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@pytest.mark.parametrize(
|
|
38
|
+
("dispatch_unit", "definition_unit", "values", "expected"),
|
|
39
|
+
[
|
|
40
|
+
(DateUnit.YEAR, DateUnit.YEAR, [1.0], [3.0]),
|
|
41
|
+
(DateUnit.YEAR, DateUnit.MONTH, [1.0], [36.0]),
|
|
42
|
+
(DateUnit.YEAR, DateUnit.DAY, [1.0], [1096.0]),
|
|
43
|
+
(DateUnit.YEAR, DateUnit.WEEK, [1.0], [157.0]),
|
|
44
|
+
(DateUnit.YEAR, DateUnit.WEEKDAY, [1.0], [1096.0]),
|
|
45
|
+
(DateUnit.MONTH, DateUnit.YEAR, [1.0], [1.0]),
|
|
46
|
+
(DateUnit.MONTH, DateUnit.MONTH, [1.0], [3.0]),
|
|
47
|
+
(DateUnit.MONTH, DateUnit.DAY, [1.0], [90.0]),
|
|
48
|
+
(DateUnit.MONTH, DateUnit.WEEK, [1.0], [13.0]),
|
|
49
|
+
(DateUnit.MONTH, DateUnit.WEEKDAY, [1.0], [90.0]),
|
|
50
|
+
(DateUnit.DAY, DateUnit.YEAR, [1.0], [1.0]),
|
|
51
|
+
(DateUnit.DAY, DateUnit.MONTH, [1.0], [1.0]),
|
|
52
|
+
(DateUnit.DAY, DateUnit.DAY, [1.0], [3.0]),
|
|
53
|
+
(DateUnit.DAY, DateUnit.WEEK, [1.0], [1.0]),
|
|
54
|
+
(DateUnit.DAY, DateUnit.WEEKDAY, [1.0], [3.0]),
|
|
55
|
+
(DateUnit.WEEK, DateUnit.YEAR, [1.0], [1.0]),
|
|
56
|
+
(DateUnit.WEEK, DateUnit.MONTH, [1.0], [1.0]),
|
|
57
|
+
(DateUnit.WEEK, DateUnit.DAY, [1.0], [21.0]),
|
|
58
|
+
(DateUnit.WEEK, DateUnit.WEEK, [1.0], [3.0]),
|
|
59
|
+
(DateUnit.WEEK, DateUnit.WEEKDAY, [1.0], [21.0]),
|
|
60
|
+
(DateUnit.WEEKDAY, DateUnit.YEAR, [1.0], [1.0]),
|
|
61
|
+
(DateUnit.WEEKDAY, DateUnit.MONTH, [1.0], [1.0]),
|
|
62
|
+
(DateUnit.WEEKDAY, DateUnit.DAY, [1.0], [3.0]),
|
|
63
|
+
(DateUnit.WEEKDAY, DateUnit.WEEK, [1.0], [1.0]),
|
|
64
|
+
(DateUnit.WEEKDAY, DateUnit.WEEKDAY, [1.0], [3.0]),
|
|
65
|
+
],
|
|
66
|
+
)
|
|
67
|
+
def test_set_input_dispatch_by_period(
|
|
68
|
+
Income,
|
|
69
|
+
population,
|
|
70
|
+
dispatch_unit,
|
|
71
|
+
definition_unit,
|
|
72
|
+
values,
|
|
73
|
+
expected,
|
|
74
|
+
) -> None:
|
|
75
|
+
Income.definition_period = definition_unit
|
|
76
|
+
income = Income()
|
|
77
|
+
holder = Holder(income, population)
|
|
78
|
+
instant = Instant((2022, 1, 1))
|
|
79
|
+
dispatch_period = Period((dispatch_unit, instant, 3))
|
|
80
|
+
|
|
81
|
+
holders.set_input_dispatch_by_period(holder, dispatch_period, values)
|
|
82
|
+
total = sum(map(holder.get_array, holder.get_known_periods()))
|
|
83
|
+
|
|
84
|
+
tools.assert_near(total, expected, absolute_error_margin=0.001)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@pytest.mark.parametrize(
|
|
88
|
+
("divide_unit", "definition_unit", "values", "expected"),
|
|
89
|
+
[
|
|
90
|
+
(DateUnit.YEAR, DateUnit.YEAR, [3.0], [1.0]),
|
|
91
|
+
(DateUnit.YEAR, DateUnit.MONTH, [36.0], [1.0]),
|
|
92
|
+
(DateUnit.YEAR, DateUnit.DAY, [1095.0], [1.0]),
|
|
93
|
+
(DateUnit.YEAR, DateUnit.WEEK, [157.0], [1.0]),
|
|
94
|
+
(DateUnit.YEAR, DateUnit.WEEKDAY, [1095.0], [1.0]),
|
|
95
|
+
(DateUnit.MONTH, DateUnit.YEAR, [1.0], [1.0]),
|
|
96
|
+
(DateUnit.MONTH, DateUnit.MONTH, [3.0], [1.0]),
|
|
97
|
+
(DateUnit.MONTH, DateUnit.DAY, [90.0], [1.0]),
|
|
98
|
+
(DateUnit.MONTH, DateUnit.WEEK, [13.0], [1.0]),
|
|
99
|
+
(DateUnit.MONTH, DateUnit.WEEKDAY, [90.0], [1.0]),
|
|
100
|
+
(DateUnit.DAY, DateUnit.YEAR, [1.0], [1.0]),
|
|
101
|
+
(DateUnit.DAY, DateUnit.MONTH, [1.0], [1.0]),
|
|
102
|
+
(DateUnit.DAY, DateUnit.DAY, [3.0], [1.0]),
|
|
103
|
+
(DateUnit.DAY, DateUnit.WEEK, [1.0], [1.0]),
|
|
104
|
+
(DateUnit.DAY, DateUnit.WEEKDAY, [3.0], [1.0]),
|
|
105
|
+
(DateUnit.WEEK, DateUnit.YEAR, [1.0], [1.0]),
|
|
106
|
+
(DateUnit.WEEK, DateUnit.MONTH, [1.0], [1.0]),
|
|
107
|
+
(DateUnit.WEEK, DateUnit.DAY, [21.0], [1.0]),
|
|
108
|
+
(DateUnit.WEEK, DateUnit.WEEK, [3.0], [1.0]),
|
|
109
|
+
(DateUnit.WEEK, DateUnit.WEEKDAY, [21.0], [1.0]),
|
|
110
|
+
(DateUnit.WEEKDAY, DateUnit.YEAR, [1.0], [1.0]),
|
|
111
|
+
(DateUnit.WEEKDAY, DateUnit.MONTH, [1.0], [1.0]),
|
|
112
|
+
(DateUnit.WEEKDAY, DateUnit.DAY, [3.0], [1.0]),
|
|
113
|
+
(DateUnit.WEEKDAY, DateUnit.WEEK, [1.0], [1.0]),
|
|
114
|
+
(DateUnit.WEEKDAY, DateUnit.WEEKDAY, [3.0], [1.0]),
|
|
115
|
+
],
|
|
116
|
+
)
|
|
117
|
+
def test_set_input_divide_by_period(
|
|
118
|
+
Income,
|
|
119
|
+
population,
|
|
120
|
+
divide_unit,
|
|
121
|
+
definition_unit,
|
|
122
|
+
values,
|
|
123
|
+
expected,
|
|
124
|
+
) -> None:
|
|
125
|
+
Income.definition_period = definition_unit
|
|
126
|
+
income = Income()
|
|
127
|
+
holder = Holder(income, population)
|
|
128
|
+
instant = Instant((2022, 1, 1))
|
|
129
|
+
divide_period = Period((divide_unit, instant, 3))
|
|
130
|
+
|
|
131
|
+
holders.set_input_divide_by_period(holder, divide_period, values)
|
|
132
|
+
last = holder.get_array(holder.get_known_periods()[-1])
|
|
133
|
+
|
|
134
|
+
tools.assert_near(last, expected, absolute_error_margin=0.001)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Enumerations for variables with a limited set of possible values."""
|
|
2
|
+
|
|
3
|
+
from . import types
|
|
4
|
+
from ._enum_type import EnumType
|
|
5
|
+
from ._errors import EnumEncodingError, EnumMemberNotFoundError
|
|
6
|
+
from .config import ENUM_ARRAY_DTYPE
|
|
7
|
+
from .enum import Enum
|
|
8
|
+
from .enum_array import EnumArray
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"ENUM_ARRAY_DTYPE",
|
|
12
|
+
"Enum",
|
|
13
|
+
"EnumArray",
|
|
14
|
+
"EnumEncodingError",
|
|
15
|
+
"EnumMemberNotFoundError",
|
|
16
|
+
"EnumType",
|
|
17
|
+
"types",
|
|
18
|
+
]
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import final
|
|
4
|
+
|
|
5
|
+
import numpy
|
|
6
|
+
|
|
7
|
+
from . import types as t
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@final
|
|
11
|
+
class EnumType(t.EnumType):
|
|
12
|
+
"""Meta class for creating an indexed :class:`.Enum`.
|
|
13
|
+
|
|
14
|
+
Examples:
|
|
15
|
+
>>> from openfisca_core import indexed_enums as enum
|
|
16
|
+
|
|
17
|
+
>>> class Enum(enum.Enum, metaclass=enum.EnumType):
|
|
18
|
+
... pass
|
|
19
|
+
|
|
20
|
+
>>> Enum.items
|
|
21
|
+
Traceback (most recent call last):
|
|
22
|
+
AttributeError: ...
|
|
23
|
+
|
|
24
|
+
>>> class Housing(Enum):
|
|
25
|
+
... OWNER = "Owner"
|
|
26
|
+
... TENANT = "Tenant"
|
|
27
|
+
|
|
28
|
+
>>> Housing.indices
|
|
29
|
+
array([0, 1], dtype=int16)
|
|
30
|
+
|
|
31
|
+
>>> Housing.names
|
|
32
|
+
array(['OWNER', 'TENANT'], dtype='<U6')
|
|
33
|
+
|
|
34
|
+
>>> Housing.enums
|
|
35
|
+
array([Housing.OWNER, Housing.TENANT], dtype=object)
|
|
36
|
+
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __new__(
|
|
40
|
+
metacls,
|
|
41
|
+
name: str,
|
|
42
|
+
bases: tuple[type, ...],
|
|
43
|
+
classdict: t.EnumDict,
|
|
44
|
+
**kwds: object,
|
|
45
|
+
) -> t.EnumType:
|
|
46
|
+
"""Create a new indexed enum class."""
|
|
47
|
+
# Create the enum class.
|
|
48
|
+
cls = super().__new__(metacls, name, bases, classdict, **kwds)
|
|
49
|
+
|
|
50
|
+
# If the enum class has no members, return it as is.
|
|
51
|
+
if not cls.__members__:
|
|
52
|
+
return cls
|
|
53
|
+
|
|
54
|
+
# Add the indices attribute to the enum class.
|
|
55
|
+
cls.indices = numpy.arange(len(cls), dtype=t.EnumDType)
|
|
56
|
+
|
|
57
|
+
# Add the names attribute to the enum class.
|
|
58
|
+
cls.names = numpy.array(cls._member_names_, dtype=t.StrDType)
|
|
59
|
+
|
|
60
|
+
# Add the enums attribute to the enum class.
|
|
61
|
+
cls.enums = numpy.array(cls, dtype=t.ObjDType)
|
|
62
|
+
|
|
63
|
+
# Return the modified enum class.
|
|
64
|
+
return cls
|
|
65
|
+
|
|
66
|
+
def __dir__(cls) -> list[str]:
|
|
67
|
+
return sorted({"indices", "names", "enums", *super().__dir__()})
|
|
68
|
+
|
|
69
|
+
def __hash__(cls) -> int:
|
|
70
|
+
return object.__hash__(cls.__name__)
|
|
71
|
+
|
|
72
|
+
def __eq__(cls, other: object) -> bool:
|
|
73
|
+
return hash(cls) == hash(other)
|
|
74
|
+
|
|
75
|
+
def __ne__(cls, other: object) -> bool:
|
|
76
|
+
return hash(cls) != hash(other)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
__all__ = ["EnumType"]
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from . import types as t
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class EnumEncodingError(TypeError):
|
|
7
|
+
"""Raised when an enum is encoded with an unsupported type."""
|
|
8
|
+
|
|
9
|
+
def __init__(
|
|
10
|
+
self, enum_class: type[t.Enum], value: t.VarArray | t.ArrayLike[object]
|
|
11
|
+
) -> None:
|
|
12
|
+
msg = (
|
|
13
|
+
f"Failed to encode \"{value}\" of type '{value[0].__class__.__name__}', "
|
|
14
|
+
"as it is not supported. Please, try again with an array of "
|
|
15
|
+
f"'{int.__name__}', '{str.__name__}', or '{enum_class.__name__}'."
|
|
16
|
+
)
|
|
17
|
+
super().__init__(msg)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class EnumMemberNotFoundError(IndexError):
|
|
21
|
+
"""Raised when a member is not found in an enum."""
|
|
22
|
+
|
|
23
|
+
def __init__(self, enum_class: type[t.Enum]) -> None:
|
|
24
|
+
index = [str(enum.index) for enum in enum_class]
|
|
25
|
+
names = [enum.name for enum in enum_class]
|
|
26
|
+
msg = (
|
|
27
|
+
f"Some members were not found in enum '{enum_class.__name__}'. "
|
|
28
|
+
f"Possible values are: {', '.join(names[:-1])}, and {names[-1]!s}; "
|
|
29
|
+
f"or their corresponding indices: {', '.join(index[:-1])}, and "
|
|
30
|
+
f"{index[-1]}."
|
|
31
|
+
)
|
|
32
|
+
super().__init__(msg)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
__all__ = ["EnumEncodingError", "EnumMemberNotFoundError"]
|