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.
Files changed (237) hide show
  1. openfisca_core/__init__.py +0 -0
  2. openfisca_core/commons/__init__.py +18 -0
  3. openfisca_core/commons/dummy.py +25 -0
  4. openfisca_core/commons/formulas.py +121 -0
  5. openfisca_core/commons/misc.py +109 -0
  6. openfisca_core/commons/py.typed +0 -0
  7. openfisca_core/commons/rates.py +114 -0
  8. openfisca_core/commons/tests/__init__.py +0 -0
  9. openfisca_core/commons/tests/test_dummy.py +9 -0
  10. openfisca_core/commons/tests/test_formulas.py +75 -0
  11. openfisca_core/commons/tests/test_rates.py +26 -0
  12. openfisca_core/data_storage/__init__.py +7 -0
  13. openfisca_core/data_storage/in_memory_storage.py +198 -0
  14. openfisca_core/data_storage/on_disk_storage.py +307 -0
  15. openfisca_core/data_storage/types.py +14 -0
  16. openfisca_core/entities/__init__.py +23 -0
  17. openfisca_core/entities/_core_entity.py +217 -0
  18. openfisca_core/entities/_description.py +55 -0
  19. openfisca_core/entities/entity.py +60 -0
  20. openfisca_core/entities/group_entity.py +123 -0
  21. openfisca_core/entities/helpers.py +165 -0
  22. openfisca_core/entities/py.typed +0 -0
  23. openfisca_core/entities/role.py +92 -0
  24. openfisca_core/entities/tests/__init__.py +0 -0
  25. openfisca_core/entities/tests/test_entity.py +10 -0
  26. openfisca_core/entities/tests/test_group_entity.py +70 -0
  27. openfisca_core/entities/tests/test_role.py +11 -0
  28. openfisca_core/entities/types.py +44 -0
  29. openfisca_core/errors/__init__.py +64 -0
  30. openfisca_core/errors/cycle_error.py +2 -0
  31. openfisca_core/errors/empty_argument_error.py +39 -0
  32. openfisca_core/errors/nan_creation_error.py +2 -0
  33. openfisca_core/errors/parameter_not_found_error.py +16 -0
  34. openfisca_core/errors/parameter_parsing_error.py +18 -0
  35. openfisca_core/errors/period_mismatch_error.py +9 -0
  36. openfisca_core/errors/situation_parsing_error.py +26 -0
  37. openfisca_core/errors/spiral_error.py +2 -0
  38. openfisca_core/errors/variable_name_config_error.py +2 -0
  39. openfisca_core/errors/variable_not_found_error.py +29 -0
  40. openfisca_core/experimental/__init__.py +9 -0
  41. openfisca_core/experimental/_errors.py +5 -0
  42. openfisca_core/experimental/_memory_config.py +41 -0
  43. openfisca_core/formula_helpers.py +9 -0
  44. openfisca_core/holders/__init__.py +33 -0
  45. openfisca_core/holders/helpers.py +93 -0
  46. openfisca_core/holders/holder.py +328 -0
  47. openfisca_core/holders/tests/__init__.py +0 -0
  48. openfisca_core/holders/tests/test_helpers.py +134 -0
  49. openfisca_core/holders/types.py +3 -0
  50. openfisca_core/indexed_enums/__init__.py +18 -0
  51. openfisca_core/indexed_enums/_enum_type.py +79 -0
  52. openfisca_core/indexed_enums/_errors.py +35 -0
  53. openfisca_core/indexed_enums/_guards.py +209 -0
  54. openfisca_core/indexed_enums/_utils.py +180 -0
  55. openfisca_core/indexed_enums/config.py +6 -0
  56. openfisca_core/indexed_enums/enum.py +218 -0
  57. openfisca_core/indexed_enums/enum_array.py +316 -0
  58. openfisca_core/indexed_enums/py.typed +0 -0
  59. openfisca_core/indexed_enums/tests/__init__.py +0 -0
  60. openfisca_core/indexed_enums/tests/test_enum.py +135 -0
  61. openfisca_core/indexed_enums/tests/test_enum_array.py +30 -0
  62. openfisca_core/indexed_enums/types.py +49 -0
  63. openfisca_core/memory_config.py +9 -0
  64. openfisca_core/model_api.py +71 -0
  65. openfisca_core/parameters/__init__.py +71 -0
  66. openfisca_core/parameters/at_instant_like.py +17 -0
  67. openfisca_core/parameters/config.py +55 -0
  68. openfisca_core/parameters/helpers.py +106 -0
  69. openfisca_core/parameters/parameter.py +217 -0
  70. openfisca_core/parameters/parameter_at_instant.py +75 -0
  71. openfisca_core/parameters/parameter_node.py +176 -0
  72. openfisca_core/parameters/parameter_node_at_instant.py +57 -0
  73. openfisca_core/parameters/parameter_scale.py +121 -0
  74. openfisca_core/parameters/parameter_scale_bracket.py +7 -0
  75. openfisca_core/parameters/values_history.py +5 -0
  76. openfisca_core/parameters/vectorial_parameter_node_at_instant.py +179 -0
  77. openfisca_core/periods/__init__.py +77 -0
  78. openfisca_core/periods/_errors.py +28 -0
  79. openfisca_core/periods/_parsers.py +122 -0
  80. openfisca_core/periods/config.py +20 -0
  81. openfisca_core/periods/date_unit.py +110 -0
  82. openfisca_core/periods/helpers.py +314 -0
  83. openfisca_core/periods/instant_.py +226 -0
  84. openfisca_core/periods/period_.py +919 -0
  85. openfisca_core/periods/py.typed +0 -0
  86. openfisca_core/periods/tests/__init__.py +0 -0
  87. openfisca_core/periods/tests/helpers/__init__.py +0 -0
  88. openfisca_core/periods/tests/helpers/test_helpers.py +65 -0
  89. openfisca_core/periods/tests/helpers/test_instant.py +73 -0
  90. openfisca_core/periods/tests/helpers/test_period.py +134 -0
  91. openfisca_core/periods/tests/test_instant.py +32 -0
  92. openfisca_core/periods/tests/test_parsers.py +129 -0
  93. openfisca_core/periods/tests/test_period.py +283 -0
  94. openfisca_core/populations/__init__.py +64 -0
  95. openfisca_core/populations/_core_population.py +454 -0
  96. openfisca_core/populations/_errors.py +65 -0
  97. openfisca_core/populations/group_population.py +326 -0
  98. openfisca_core/populations/population.py +142 -0
  99. openfisca_core/populations/tests/__init__.py +0 -0
  100. openfisca_core/populations/tests/test_members_position.py +116 -0
  101. openfisca_core/populations/types.py +110 -0
  102. openfisca_core/projectors/__init__.py +39 -0
  103. openfisca_core/projectors/entity_to_person_projector.py +12 -0
  104. openfisca_core/projectors/first_person_to_entity_projector.py +13 -0
  105. openfisca_core/projectors/helpers.py +139 -0
  106. openfisca_core/projectors/projector.py +38 -0
  107. openfisca_core/projectors/typing.py +27 -0
  108. openfisca_core/projectors/unique_role_to_entity_projector.py +14 -0
  109. openfisca_core/rates.py +9 -0
  110. openfisca_core/reforms/__init__.py +24 -0
  111. openfisca_core/reforms/in_yaml_test_reform.py +28 -0
  112. openfisca_core/reforms/reform.py +86 -0
  113. openfisca_core/scripts/__init__.py +106 -0
  114. openfisca_core/scripts/__pycache__/__init__.cpython-310-pytest-8.3.3.pyc +0 -0
  115. openfisca_core/scripts/__pycache__/__init__.cpython-310-pytest-8.4.2.pyc +0 -0
  116. openfisca_core/scripts/__pycache__/__init__.cpython-310.pyc +0 -0
  117. openfisca_core/scripts/__pycache__/__init__.cpython-311-pytest-8.3.3.pyc +0 -0
  118. openfisca_core/scripts/__pycache__/__init__.cpython-311-pytest-8.4.2.pyc +0 -0
  119. openfisca_core/scripts/__pycache__/__init__.cpython-311.pyc +0 -0
  120. openfisca_core/scripts/__pycache__/__init__.cpython-312-pytest-8.4.2.pyc +0 -0
  121. openfisca_core/scripts/__pycache__/__init__.cpython-312.pyc +0 -0
  122. openfisca_core/scripts/__pycache__/__init__.cpython-313-pytest-8.3.3.pyc +0 -0
  123. openfisca_core/scripts/__pycache__/__init__.cpython-313-pytest-8.4.2.pyc +0 -0
  124. openfisca_core/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
  125. openfisca_core/scripts/__pycache__/find_placeholders.cpython-311.pyc +0 -0
  126. openfisca_core/scripts/__pycache__/find_placeholders.cpython-313.pyc +0 -0
  127. openfisca_core/scripts/__pycache__/measure_numpy_condition_notations.cpython-311.pyc +0 -0
  128. openfisca_core/scripts/__pycache__/measure_numpy_condition_notations.cpython-313.pyc +0 -0
  129. openfisca_core/scripts/__pycache__/measure_performances.cpython-311.pyc +0 -0
  130. openfisca_core/scripts/__pycache__/measure_performances.cpython-313.pyc +0 -0
  131. openfisca_core/scripts/__pycache__/measure_performances_fancy_indexing.cpython-311.pyc +0 -0
  132. openfisca_core/scripts/__pycache__/measure_performances_fancy_indexing.cpython-313.pyc +0 -0
  133. openfisca_core/scripts/__pycache__/openfisca_command.cpython-310.pyc +0 -0
  134. openfisca_core/scripts/__pycache__/openfisca_command.cpython-311.pyc +0 -0
  135. openfisca_core/scripts/__pycache__/openfisca_command.cpython-312.pyc +0 -0
  136. openfisca_core/scripts/__pycache__/openfisca_command.cpython-313.pyc +0 -0
  137. openfisca_core/scripts/__pycache__/remove_fuzzy.cpython-311.pyc +0 -0
  138. openfisca_core/scripts/__pycache__/remove_fuzzy.cpython-313.pyc +0 -0
  139. openfisca_core/scripts/__pycache__/run_test.cpython-310.pyc +0 -0
  140. openfisca_core/scripts/__pycache__/run_test.cpython-311.pyc +0 -0
  141. openfisca_core/scripts/__pycache__/run_test.cpython-312.pyc +0 -0
  142. openfisca_core/scripts/__pycache__/run_test.cpython-313.pyc +0 -0
  143. openfisca_core/scripts/__pycache__/simulation_generator.cpython-311.pyc +0 -0
  144. openfisca_core/scripts/__pycache__/simulation_generator.cpython-313.pyc +0 -0
  145. openfisca_core/scripts/assets/__init__.py +0 -0
  146. openfisca_core/scripts/assets/__pycache__/__init__.cpython-311.pyc +0 -0
  147. openfisca_core/scripts/assets/__pycache__/__init__.cpython-313.pyc +0 -0
  148. openfisca_core/scripts/assets/index.html +140 -0
  149. openfisca_core/scripts/find_placeholders.py +51 -0
  150. openfisca_core/scripts/measure_numpy_condition_notations.py +137 -0
  151. openfisca_core/scripts/measure_performances.py +291 -0
  152. openfisca_core/scripts/measure_performances_fancy_indexing.py +89 -0
  153. openfisca_core/scripts/migrations/__init__.py +0 -0
  154. openfisca_core/scripts/migrations/__pycache__/__init__.cpython-311.pyc +0 -0
  155. openfisca_core/scripts/migrations/__pycache__/__init__.cpython-313.pyc +0 -0
  156. openfisca_core/scripts/migrations/__pycache__/v24_to_25.cpython-311.pyc +0 -0
  157. openfisca_core/scripts/migrations/__pycache__/v24_to_25.cpython-313.pyc +0 -0
  158. openfisca_core/scripts/migrations/v16_2_to_v17/__init__.py +0 -0
  159. openfisca_core/scripts/migrations/v16_2_to_v17/__pycache__/__init__.cpython-311.pyc +0 -0
  160. openfisca_core/scripts/migrations/v16_2_to_v17/__pycache__/__init__.cpython-313.pyc +0 -0
  161. openfisca_core/scripts/migrations/v16_2_to_v17/__pycache__/xml_to_yaml_country_template.cpython-311.pyc +0 -0
  162. openfisca_core/scripts/migrations/v16_2_to_v17/__pycache__/xml_to_yaml_country_template.cpython-313.pyc +0 -0
  163. openfisca_core/scripts/migrations/v16_2_to_v17/__pycache__/xml_to_yaml_extension_template.cpython-311.pyc +0 -0
  164. openfisca_core/scripts/migrations/v16_2_to_v17/__pycache__/xml_to_yaml_extension_template.cpython-313.pyc +0 -0
  165. openfisca_core/scripts/migrations/v16_2_to_v17/legislation.xsd +102 -0
  166. openfisca_core/scripts/migrations/v16_2_to_v17/xml_to_yaml_country_template.py +30 -0
  167. openfisca_core/scripts/migrations/v16_2_to_v17/xml_to_yaml_extension_template.py +26 -0
  168. openfisca_core/scripts/migrations/v24_to_25.py +156 -0
  169. openfisca_core/scripts/openfisca_command.py +192 -0
  170. openfisca_core/scripts/remove_fuzzy.py +213 -0
  171. openfisca_core/scripts/run_test.py +51 -0
  172. openfisca_core/scripts/simulation_generator.py +90 -0
  173. openfisca_core/simulation_builder.py +16 -0
  174. openfisca_core/simulations/__init__.py +45 -0
  175. openfisca_core/simulations/_build_default_simulation.py +158 -0
  176. openfisca_core/simulations/_build_from_variables.py +230 -0
  177. openfisca_core/simulations/_type_guards.py +298 -0
  178. openfisca_core/simulations/helpers.py +111 -0
  179. openfisca_core/simulations/simulation.py +618 -0
  180. openfisca_core/simulations/simulation_builder.py +855 -0
  181. openfisca_core/simulations/typing.py +203 -0
  182. openfisca_core/taxbenefitsystems/__init__.py +26 -0
  183. openfisca_core/taxbenefitsystems/tax_benefit_system.py +584 -0
  184. openfisca_core/taxscales/__init__.py +35 -0
  185. openfisca_core/taxscales/abstract_rate_tax_scale.py +41 -0
  186. openfisca_core/taxscales/abstract_tax_scale.py +53 -0
  187. openfisca_core/taxscales/amount_tax_scale_like.py +55 -0
  188. openfisca_core/taxscales/helpers.py +44 -0
  189. openfisca_core/taxscales/linear_average_rate_tax_scale.py +76 -0
  190. openfisca_core/taxscales/marginal_amount_tax_scale.py +34 -0
  191. openfisca_core/taxscales/marginal_rate_tax_scale.py +286 -0
  192. openfisca_core/taxscales/rate_tax_scale_like.py +214 -0
  193. openfisca_core/taxscales/single_amount_tax_scale.py +32 -0
  194. openfisca_core/taxscales/tax_scale_like.py +65 -0
  195. openfisca_core/tools/__init__.py +94 -0
  196. openfisca_core/tools/parallel_plugin.py +55 -0
  197. openfisca_core/tools/simulation_dumper.py +137 -0
  198. openfisca_core/tools/test_runner.py +802 -0
  199. openfisca_core/tracers/__init__.py +40 -0
  200. openfisca_core/tracers/computation_log.py +196 -0
  201. openfisca_core/tracers/flat_trace.py +87 -0
  202. openfisca_core/tracers/full_tracer.py +172 -0
  203. openfisca_core/tracers/performance_log.py +133 -0
  204. openfisca_core/tracers/simple_tracer.py +64 -0
  205. openfisca_core/tracers/trace_node.py +121 -0
  206. openfisca_core/tracers/tracing_parameter_node_at_instant.py +81 -0
  207. openfisca_core/types.py +548 -0
  208. openfisca_core/variables/__init__.py +26 -0
  209. openfisca_core/variables/config.py +53 -0
  210. openfisca_core/variables/helpers.py +67 -0
  211. openfisca_core/variables/tests/__init__.py +0 -0
  212. openfisca_core/variables/tests/test_definition_period.py +43 -0
  213. openfisca_core/variables/variable.py +474 -0
  214. openfisca_core/warnings/__init__.py +25 -0
  215. openfisca_core/warnings/libyaml_warning.py +2 -0
  216. openfisca_core/warnings/tempfile_warning.py +2 -0
  217. openfisca_core_test-46.0.0.data/data/share/openfisca/openfisca-core/CHANGELOG.md +4209 -0
  218. openfisca_core_test-46.0.0.data/data/share/openfisca/openfisca-core/README.md +152 -0
  219. openfisca_core_test-46.0.0.dist-info/METADATA +194 -0
  220. openfisca_core_test-46.0.0.dist-info/RECORD +237 -0
  221. openfisca_core_test-46.0.0.dist-info/WHEEL +5 -0
  222. openfisca_core_test-46.0.0.dist-info/entry_points.txt +3 -0
  223. openfisca_core_test-46.0.0.dist-info/licenses/LICENSE +661 -0
  224. openfisca_core_test-46.0.0.dist-info/top_level.txt +2 -0
  225. openfisca_web_api/__init__.py +0 -0
  226. openfisca_web_api/app.py +226 -0
  227. openfisca_web_api/errors.py +11 -0
  228. openfisca_web_api/handlers.py +90 -0
  229. openfisca_web_api/loader/__init__.py +22 -0
  230. openfisca_web_api/loader/entities.py +28 -0
  231. openfisca_web_api/loader/parameters.py +122 -0
  232. openfisca_web_api/loader/spec.py +168 -0
  233. openfisca_web_api/loader/tax_benefit_system.py +27 -0
  234. openfisca_web_api/loader/variables.py +121 -0
  235. openfisca_web_api/openAPI.yml +434 -0
  236. openfisca_web_api/scripts/__init__.py +0 -0
  237. openfisca_web_api/scripts/serve.py +99 -0
@@ -0,0 +1,584 @@
1
+ from __future__ import annotations
2
+
3
+ import ast
4
+ import copy
5
+ import functools
6
+ import glob
7
+ import importlib
8
+ import importlib.metadata
9
+ import importlib.util
10
+ import inspect
11
+ import linecache
12
+ import logging
13
+ import os
14
+ import sys
15
+ import traceback
16
+ from collections.abc import Sequence
17
+ from typing import Any
18
+
19
+ from openfisca_core import commons, periods, variables
20
+ from openfisca_core.entities import Entity
21
+ from openfisca_core.errors import VariableNameConflictError, VariableNotFoundError
22
+ from openfisca_core.parameters import ParameterNode
23
+ from openfisca_core.periods import Instant, Period
24
+ from openfisca_core.populations import GroupPopulation, Population
25
+ from openfisca_core.simulations import SimulationBuilder
26
+ from openfisca_core.types import ParameterNodeAtInstant
27
+ from openfisca_core.variables import Variable
28
+
29
+ log = logging.getLogger(__name__)
30
+
31
+
32
+ class TaxBenefitSystem:
33
+ """Represents the legislation.
34
+
35
+ It stores parameters (values defined for everyone) and variables (values
36
+ defined for some given entity e.g. a person).
37
+
38
+ Attributes:
39
+ parameters: Directory containing the YAML parameter files.
40
+
41
+ Args:
42
+ entities: Entities used by the tax benefit system.
43
+
44
+ """
45
+
46
+ person_entity: Entity
47
+
48
+ _base_tax_benefit_system = None
49
+ _parameters_at_instant_cache: dict[Instant, ParameterNodeAtInstant] = {}
50
+ person_key_plural = None
51
+ preprocess_parameters = None
52
+ baseline = None # Baseline tax-benefit system. Used only by reforms. Note: Reforms can be chained.
53
+ cache_blacklist = None
54
+ decomposition_file_path = None
55
+
56
+ def __init__(self, entities: Sequence[Entity]) -> None:
57
+ # TODO: Currently: Don't use a weakref, because they are cleared by Paste (at least) at each call.
58
+ self.parameters: ParameterNode | None = None
59
+ self.variables: dict[Any, Any] = {}
60
+ self.open_api_config: dict[Any, Any] = {}
61
+ # Tax benefit systems are mutable, so entities (which need to know about our variables) can't be shared among them
62
+ if entities is None or len(entities) == 0:
63
+ msg = "A tax and benefit system must have at least an entity."
64
+ raise Exception(msg)
65
+ self.entities = [copy.copy(entity) for entity in entities]
66
+ self.person_entity = next(
67
+ entity for entity in self.entities if entity.is_person
68
+ )
69
+ self.group_entities = [
70
+ entity for entity in self.entities if not entity.is_person
71
+ ]
72
+ for entity in self.entities:
73
+ entity.set_tax_benefit_system(self)
74
+
75
+ @property
76
+ def base_tax_benefit_system(self):
77
+ base_tax_benefit_system = self._base_tax_benefit_system
78
+ if base_tax_benefit_system is None:
79
+ baseline = self.baseline
80
+ if baseline is None:
81
+ return self
82
+ self._base_tax_benefit_system = base_tax_benefit_system = (
83
+ baseline.base_tax_benefit_system
84
+ )
85
+ return base_tax_benefit_system
86
+
87
+ def instantiate_entities(self):
88
+ person = self.person_entity
89
+ members = Population(person)
90
+ entities: dict[Entity.key, Entity] = {person.key: members}
91
+
92
+ for entity in self.group_entities:
93
+ entities[entity.key] = GroupPopulation(entity, members)
94
+
95
+ return entities
96
+
97
+ # Deprecated method of constructing simulations, to be phased out in favor of SimulationBuilder
98
+ def new_scenario(self):
99
+ class ScenarioAdapter:
100
+ def __init__(self, tax_benefit_system) -> None:
101
+ self.tax_benefit_system = tax_benefit_system
102
+
103
+ def init_from_attributes(self, **attributes):
104
+ self.attributes = attributes
105
+ return self
106
+
107
+ def init_from_dict(self, dict):
108
+ self.attributes = None
109
+ self.dict = dict
110
+ self.period = dict.pop("period")
111
+ return self
112
+
113
+ def new_simulation(
114
+ self,
115
+ debug=False,
116
+ opt_out_cache=False,
117
+ use_baseline=False,
118
+ trace=False,
119
+ ):
120
+ # Legacy from scenarios, used in reforms
121
+ tax_benefit_system = self.tax_benefit_system
122
+ if use_baseline:
123
+ while True:
124
+ baseline = tax_benefit_system.baseline
125
+ if baseline is None:
126
+ break
127
+ tax_benefit_system = baseline
128
+
129
+ builder = SimulationBuilder()
130
+ if self.attributes:
131
+ variables = self.attributes.get("input_variables") or {}
132
+ period = self.attributes.get("period")
133
+ builder.set_default_period(period)
134
+ simulation = builder.build_from_variables(
135
+ tax_benefit_system,
136
+ variables,
137
+ )
138
+ else:
139
+ builder.set_default_period(self.period)
140
+ simulation = builder.build_from_entities(
141
+ tax_benefit_system,
142
+ self.dict,
143
+ )
144
+
145
+ simulation.trace = trace
146
+ simulation.debug = debug
147
+ simulation.opt_out_cache = opt_out_cache
148
+
149
+ return simulation
150
+
151
+ return ScenarioAdapter(self)
152
+
153
+ def prefill_cache(self) -> None:
154
+ pass
155
+
156
+ def load_variable(self, variable_class, update=False):
157
+ name = variable_class.__name__
158
+
159
+ # Check if a Variable with the same name is already registered.
160
+ baseline_variable = self.get_variable(name)
161
+ if baseline_variable and not update:
162
+ msg = f'Variable "{name}" is already defined. Use `update_variable` to replace it.'
163
+ raise VariableNameConflictError(
164
+ msg,
165
+ )
166
+
167
+ variable = variable_class(baseline_variable=baseline_variable)
168
+ self.variables[variable.name] = variable
169
+
170
+ return variable
171
+
172
+ def add_variable(self, variable: Variable) -> Variable:
173
+ """Adds an OpenFisca variable to the tax and benefit system.
174
+
175
+ Args:
176
+ variable: The variable to add. Must be a subclass of Variable.
177
+
178
+ Raises:
179
+ openfisca_core.errors.VariableNameConflictError: if a variable with the same name have previously been added to the tax and benefit system.
180
+
181
+ """
182
+ return self.load_variable(variable, update=False)
183
+
184
+ def replace_variable(self, variable: Variable) -> None:
185
+ """Replaces an existing variable by a new one.
186
+
187
+ The new variable must have the same name than the replaced one.
188
+
189
+ If no variable with the given name exists in the Tax-Benefit system, no
190
+ error will be raised and the new variable will be simply added.
191
+
192
+ Args:
193
+ variable: The variable to replace.
194
+
195
+ """
196
+ name = variable.__name__
197
+
198
+ if self.variables.get(name) is not None:
199
+ del self.variables[name]
200
+
201
+ self.load_variable(variable, update=False)
202
+
203
+ def update_variable(self, variable: Variable) -> Variable:
204
+ """Update an existing variable in the Tax-Benefit system.
205
+
206
+ All attributes of the updated variable that are not explicitly
207
+ overridden by the new ``variable`` will stay unchanged.
208
+
209
+ The new variable must have the same name than the updated one.
210
+
211
+ If no variable with the given name exists in the tax and benefit
212
+ system, no error will be raised and the new variable will be simply
213
+ added.
214
+
215
+ Args:
216
+ variable: Variable to add. Must be a subclass of Variable.
217
+
218
+ Returns:
219
+ The added variable.
220
+
221
+ """
222
+ return self.load_variable(variable, update=True)
223
+
224
+ def add_variables_from_file(self, file_path) -> None:
225
+ """Adds all OpenFisca variables contained in a given file to the tax and benefit system."""
226
+ try:
227
+ source_file_path = file_path.replace(
228
+ self.get_package_metadata()["location"],
229
+ "",
230
+ )
231
+
232
+ file_name = os.path.splitext(os.path.basename(file_path))[0]
233
+
234
+ # As Python remembers loaded modules by name, in order to prevent collisions, we need to make sure that:
235
+ # - Files with the same name, but located in different directories, have a different module names. Hence the file path hash in the module name.
236
+ # - The same file, loaded by different tax and benefit systems, has distinct module names. Hence the `id(self)` in the module name.
237
+ module_name = f"{id(self)}_{hash(os.path.abspath(file_path))}_{file_name}"
238
+
239
+ try:
240
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
241
+ module = importlib.util.module_from_spec(spec)
242
+ sys.modules[module_name] = module
243
+
244
+ lines = linecache.getlines(file_path, module.__dict__)
245
+ source = "".join(lines)
246
+ tree = ast.parse(source)
247
+ defs = {i.name: i for i in tree.body if isinstance(i, ast.ClassDef)}
248
+ spec.loader.exec_module(module)
249
+
250
+ except NameError:
251
+ log.exception(
252
+ "if this code used to work, this error might be due to a major change in OpenFisca-Core. Checkout the changelog to learn more: <https://github.com/openfisca/openfisca-core/blob/main/CHANGELOG.md>",
253
+ )
254
+ raise
255
+ potential_variables = [
256
+ getattr(module, item)
257
+ for item in dir(module)
258
+ if not item.startswith("__")
259
+ ]
260
+ for pot_variable in potential_variables:
261
+ # We only want to get the module classes defined in this module (not imported)
262
+ if (
263
+ inspect.isclass(pot_variable)
264
+ and issubclass(pot_variable, Variable)
265
+ and pot_variable.__module__ == module_name
266
+ ):
267
+ class_def = defs[pot_variable.__name__]
268
+ pot_variable.introspection_data = (
269
+ source_file_path,
270
+ "".join(lines[class_def.lineno - 1 : class_def.end_lineno]),
271
+ class_def.lineno - 1,
272
+ )
273
+ self.add_variable(pot_variable)
274
+ except Exception:
275
+ log.exception(f'Unable to load OpenFisca variables from file "{file_path}"')
276
+ raise
277
+
278
+ def add_variables_from_directory(self, directory) -> None:
279
+ """Recursively explores a directory, and adds all OpenFisca variables found there to the tax and benefit system."""
280
+ py_files = glob.glob(os.path.join(directory, "*.py"))
281
+ for py_file in py_files:
282
+ self.add_variables_from_file(py_file)
283
+ subdirectories = glob.glob(os.path.join(directory, "*/"))
284
+ for subdirectory in subdirectories:
285
+ self.add_variables_from_directory(subdirectory)
286
+
287
+ def add_variables(self, *variables) -> None:
288
+ """Adds a list of OpenFisca Variables to the `TaxBenefitSystem`.
289
+
290
+ See also :any:`add_variable`
291
+ """
292
+ for variable in variables:
293
+ self.add_variable(variable)
294
+
295
+ def load_extension(self, extension) -> None:
296
+ """Loads an extension to the tax and benefit system.
297
+
298
+ :param str extension: The extension to load. Can be an absolute path pointing to an extension directory, or the name of an OpenFisca extension installed as a pip package.
299
+
300
+ """
301
+ # Load extension from installed pip package
302
+ try:
303
+ package = importlib.import_module(extension)
304
+ extension_directory = package.__path__[0]
305
+ except ImportError:
306
+ message = os.linesep.join(
307
+ [
308
+ traceback.format_exc(),
309
+ f"Error loading extension: `{extension}` is neither a directory, nor a package.",
310
+ "Are you sure it is installed in your environment? If so, look at the stack trace above to determine the origin of this error.",
311
+ "See more at <https://github.com/openfisca/openfisca-extension-template#installing>.",
312
+ ],
313
+ )
314
+ raise ValueError(message)
315
+
316
+ self.add_variables_from_directory(extension_directory)
317
+ param_dir = os.path.join(extension_directory, "parameters")
318
+ if os.path.isdir(param_dir):
319
+ extension_parameters = ParameterNode(directory_path=param_dir)
320
+ self.parameters.merge(extension_parameters)
321
+
322
+ def apply_reform(self, reform_path: str) -> TaxBenefitSystem:
323
+ """Generates a new tax and benefit system applying a reform to the tax and benefit system.
324
+
325
+ The current tax and benefit system is **not** mutated.
326
+
327
+ Args:
328
+ reform_path: The reform to apply. Must respect the format *installed_package.sub_module.reform*
329
+
330
+ Returns:
331
+ TaxBenefitSystem: A reformed tax and benefit system.
332
+
333
+ Example:
334
+ >>> self.apply_reform("openfisca_france.reforms.inversion_revenus")
335
+
336
+ """
337
+ from openfisca_core.reforms import Reform
338
+
339
+ try:
340
+ reform_package, reform_name = reform_path.rsplit(".", 1)
341
+ except ValueError:
342
+ msg = f"`{reform_path}` does not seem to be a path pointing to a reform. A path looks like `some_country_package.reforms.some_reform.`"
343
+ raise ValueError(
344
+ msg,
345
+ )
346
+ try:
347
+ reform_module = importlib.import_module(reform_package)
348
+ except ImportError:
349
+ message = os.linesep.join(
350
+ [
351
+ traceback.format_exc(),
352
+ f"Could not import `{reform_package}`.",
353
+ "Are you sure of this reform module name? If so, look at the stack trace above to determine the origin of this error.",
354
+ ],
355
+ )
356
+ raise ValueError(message)
357
+ reform = getattr(reform_module, reform_name, None)
358
+ if reform is None:
359
+ msg = f"{reform_package} has no attribute {reform_name}"
360
+ raise ValueError(msg)
361
+ if not issubclass(reform, Reform):
362
+ msg = f"`{reform_path}` does not seem to be a valid Openfisca reform."
363
+ raise ValueError(
364
+ msg,
365
+ )
366
+
367
+ return reform(self)
368
+
369
+ def get_variable(
370
+ self,
371
+ variable_name: str,
372
+ check_existence: bool = False,
373
+ ) -> Variable | None:
374
+ """Get a variable from the tax and benefit system.
375
+
376
+ :param variable_name: Name of the requested variable.
377
+ :param check_existence: If True, raise an error if the requested variable does not exist.
378
+ """
379
+ variables: dict[str, Variable | None] = self.variables
380
+ variable: Variable | None = variables.get(variable_name)
381
+
382
+ if isinstance(variable, Variable):
383
+ return variable
384
+
385
+ if not isinstance(variable, Variable) and not check_existence:
386
+ return variable
387
+
388
+ raise VariableNotFoundError(variable_name, self)
389
+
390
+ def neutralize_variable(self, variable_name: str) -> None:
391
+ """Neutralizes an OpenFisca variable existing in the tax and benefit system.
392
+
393
+ A neutralized variable always returns its default value when computed.
394
+
395
+ Trying to set inputs for a neutralized variable has no effect except raising a warning.
396
+ """
397
+ self.variables[variable_name] = variables.get_neutralized_variable(
398
+ self.get_variable(variable_name),
399
+ )
400
+
401
+ def annualize_variable(
402
+ self,
403
+ variable_name: str,
404
+ period: Period | None = None,
405
+ ) -> None:
406
+ check: bool
407
+ variable: Variable | None
408
+ annualised_variable: Variable
409
+
410
+ check = bool(period)
411
+ variable = self.get_variable(variable_name, check)
412
+
413
+ if variable is None:
414
+ raise VariableNotFoundError(variable_name, self)
415
+
416
+ annualised_variable = variables.get_annualized_variable(variable)
417
+
418
+ self.variables[variable_name] = annualised_variable
419
+
420
+ def load_parameters(self, path_to_yaml_dir) -> None:
421
+ """Loads the legislation parameter for a directory containing YAML parameters files.
422
+
423
+ :param path_to_yaml_dir: Absolute path towards the YAML parameter directory.
424
+
425
+ Example:
426
+ >>> self.load_parameters("/path/to/yaml/parameters/dir")
427
+
428
+ """
429
+ parameters = ParameterNode("", directory_path=path_to_yaml_dir)
430
+
431
+ if self.preprocess_parameters is not None:
432
+ parameters = self.preprocess_parameters(parameters)
433
+
434
+ self.parameters = parameters
435
+
436
+ def _get_baseline_parameters_at_instant(self, instant):
437
+ baseline = self.baseline
438
+ if baseline is None:
439
+ return self.get_parameters_at_instant(instant)
440
+ return baseline._get_baseline_parameters_at_instant(instant)
441
+
442
+ @functools.lru_cache
443
+ def get_parameters_at_instant(
444
+ self,
445
+ instant: str | int | Period | Instant,
446
+ ) -> ParameterNodeAtInstant | None:
447
+ """Get the parameters of the legislation at a given instant.
448
+
449
+ Args:
450
+ instant: :obj:`str` formatted "YYYY-MM-DD" or :class:`~openfisca_core.periods.Instant`.
451
+
452
+ Returns:
453
+ The parameters of the legislation at a given instant.
454
+
455
+ """
456
+ key: Instant | None
457
+ msg: str
458
+
459
+ if isinstance(instant, Instant):
460
+ key = instant
461
+
462
+ elif isinstance(instant, Period):
463
+ key = instant.start
464
+
465
+ elif isinstance(instant, (str, int)):
466
+ key = periods.instant(instant)
467
+
468
+ else:
469
+ msg = f"Expected an Instant (e.g. Instant((2017, 1, 1)) ). Got: {key}."
470
+ raise AssertionError(msg)
471
+
472
+ if self.parameters is None:
473
+ return None
474
+
475
+ return self.parameters.get_at_instant(key)
476
+
477
+ def get_package_metadata(self) -> dict[str, str]:
478
+ """Gets metadata relative to the country package.
479
+
480
+ Returns:
481
+ A dictionary with the country package metadata
482
+
483
+ Example:
484
+ >>> tax_benefit_system.get_package_metadata()
485
+ >>> {
486
+ >>> 'location': '/path/to/dir/containing/package',
487
+ >>> 'name': 'openfisca-france',
488
+ >>> 'repository_url': 'https://github.com/openfisca/openfisca-france',
489
+ >>> 'version': '17.2.0'
490
+ >>> }
491
+
492
+ """
493
+ # Handle reforms
494
+ if self.baseline:
495
+ return self.baseline.get_package_metadata()
496
+
497
+ module = inspect.getmodule(self)
498
+
499
+ try:
500
+ source_file = inspect.getsourcefile(module)
501
+ package_name = module.__package__.split(".")[0]
502
+ distribution = importlib.metadata.distribution(package_name)
503
+ source_metadata = distribution.metadata
504
+ except Exception:
505
+ log.warning("Unable to load package metadata, exposing default metadata")
506
+ source_metadata = {
507
+ "Name": self.__class__.__name__,
508
+ "Version": "0.0.0",
509
+ "Home-page": "https://openfisca.org",
510
+ }
511
+
512
+ try:
513
+ source_file = inspect.getsourcefile(module)
514
+ location = source_file.split(package_name)[0].rstrip("/")
515
+ except Exception:
516
+ log.warning("Unable to load package source folder")
517
+ location = "_unknown_"
518
+
519
+ repository_url = ""
520
+ if source_metadata.get("Project-URL"): # pyproject.toml metadata format
521
+ repository_url = next(
522
+ filter(
523
+ lambda url: url.startswith("Repository"),
524
+ source_metadata.get_all("Project-URL"),
525
+ ),
526
+ ).split("Repository, ")[-1]
527
+ else: # setup.py format
528
+ repository_url = source_metadata.get("Home-page")
529
+
530
+ return {
531
+ "name": source_metadata.get("Name").lower(),
532
+ "version": source_metadata.get("Version"),
533
+ "repository_url": repository_url,
534
+ "location": location,
535
+ }
536
+
537
+ def get_variables(
538
+ self,
539
+ entity: Entity | None = None,
540
+ ) -> dict[str, Variable]:
541
+ """Gets all variables contained in a tax and benefit system.
542
+
543
+ Args:
544
+ entity: If set, returns the variable defined for the given entity.
545
+
546
+ Returns:
547
+ A dictionary, indexed by variable names.
548
+
549
+ """
550
+ if not entity:
551
+ return self.variables
552
+ return {
553
+ variable_name: variable
554
+ for variable_name, variable in self.variables.items()
555
+ # TODO - because entities are copied (see constructor) they can't be compared
556
+ if variable.entity.key == entity.key
557
+ }
558
+
559
+ def clone(self):
560
+ new = commons.empty_clone(self)
561
+ new_dict = new.__dict__
562
+
563
+ for key, value in self.__dict__.items():
564
+ if key not in (
565
+ "parameters",
566
+ "_parameters_at_instant_cache",
567
+ "variables",
568
+ "open_api_config",
569
+ ):
570
+ new_dict[key] = value
571
+ for entity in new_dict["entities"]:
572
+ entity.set_tax_benefit_system(new)
573
+
574
+ new_dict["parameters"] = self.parameters.clone()
575
+ new_dict["_parameters_at_instant_cache"] = {}
576
+ new_dict["variables"] = copy.deepcopy(self.variables)
577
+ new_dict["open_api_config"] = self.open_api_config.copy()
578
+ return new
579
+
580
+ def entities_plural(self):
581
+ return {entity.plural for entity in self.entities}
582
+
583
+ def entities_by_singular(self):
584
+ return {entity.key: entity for entity in self.entities}
@@ -0,0 +1,35 @@
1
+ # Transitional imports to ensure non-breaking changes.
2
+ # Could be deprecated in the next major release.
3
+ #
4
+ # How imports are being used today:
5
+ #
6
+ # >>> from openfisca_core.module import symbol
7
+ #
8
+ # The previous example provokes cyclic dependency problems
9
+ # that prevent us from modularizing the different components
10
+ # of the library so to make them easier to test and to maintain.
11
+ #
12
+ # How could them be used after the next major release:
13
+ #
14
+ # >>> from openfisca_core import module
15
+ # >>> module.symbol()
16
+ #
17
+ # And for classes:
18
+ #
19
+ # >>> from openfisca_core.module import Symbol
20
+ # >>> Symbol()
21
+ #
22
+ # See: https://www.python.org/dev/peps/pep-0008/#imports
23
+
24
+ from openfisca_core.errors import EmptyArgumentError # noqa: F401
25
+
26
+ from .abstract_rate_tax_scale import AbstractRateTaxScale # noqa: F401
27
+ from .abstract_tax_scale import AbstractTaxScale # noqa: F401
28
+ from .amount_tax_scale_like import AmountTaxScaleLike # noqa: F401
29
+ from .helpers import combine_tax_scales # noqa: F401
30
+ from .linear_average_rate_tax_scale import LinearAverageRateTaxScale # noqa: F401
31
+ from .marginal_amount_tax_scale import MarginalAmountTaxScale # noqa: F401
32
+ from .marginal_rate_tax_scale import MarginalRateTaxScale # noqa: F401
33
+ from .rate_tax_scale_like import RateTaxScaleLike # noqa: F401
34
+ from .single_amount_tax_scale import SingleAmountTaxScale # noqa: F401
35
+ from .tax_scale_like import TaxScaleLike # noqa: F401
@@ -0,0 +1,41 @@
1
+ from __future__ import annotations
2
+
3
+ import typing
4
+ import warnings
5
+
6
+ from .rate_tax_scale_like import RateTaxScaleLike
7
+
8
+ if typing.TYPE_CHECKING:
9
+ import numpy
10
+
11
+ NumericalArray = numpy.int32 | numpy.float32
12
+
13
+
14
+ class AbstractRateTaxScale(RateTaxScaleLike):
15
+ """Base class for various types of rate-based tax scales: marginal rate,
16
+ linear average rate...
17
+ """
18
+
19
+ def __init__(
20
+ self,
21
+ name: str | None = None,
22
+ option: typing.Any = None,
23
+ unit: typing.Any = None,
24
+ ) -> None:
25
+ message = [
26
+ "The 'AbstractRateTaxScale' class has been deprecated since",
27
+ "version 34.7.0, and will be removed in the future.",
28
+ ]
29
+
30
+ warnings.warn(" ".join(message), DeprecationWarning, stacklevel=2)
31
+ super().__init__(name, option, unit)
32
+
33
+ def calc(
34
+ self,
35
+ tax_base: NumericalArray,
36
+ right: bool,
37
+ ) -> typing.NoReturn:
38
+ msg = f"Method 'calc' is not implemented for {self.__class__.__name__}"
39
+ raise NotImplementedError(
40
+ msg,
41
+ )