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,314 @@
1
+ from __future__ import annotations
2
+
3
+ import datetime
4
+ import functools
5
+ from typing import NoReturn
6
+
7
+ import pendulum
8
+
9
+ from openfisca_core import types as t
10
+
11
+ from . import config
12
+ from ._errors import InstantError, PeriodError
13
+ from ._parsers import parse_instant, parse_period
14
+ from .date_unit import DateUnit
15
+ from .instant_ import Instant
16
+ from .period_ import Period
17
+
18
+
19
+ @functools.singledispatch
20
+ def instant(value: object) -> t.Instant:
21
+ """Build a new instant, aka a triple of integers (year, month, day).
22
+
23
+ Args:
24
+ value(object): An ``instant-like`` object.
25
+
26
+ Returns:
27
+ :obj:`.Instant`: A new instant.
28
+
29
+ Raises:
30
+ :exc:`ValueError`: When the arguments were invalid, like "2021-32-13".
31
+
32
+ Examples:
33
+ >>> instant((2021,))
34
+ Instant((2021, 1, 1))
35
+
36
+ >>> instant((2021, 9))
37
+ Instant((2021, 9, 1))
38
+
39
+ >>> instant(datetime.date(2021, 9, 16))
40
+ Instant((2021, 9, 16))
41
+
42
+ >>> instant(Instant((2021, 9, 16)))
43
+ Instant((2021, 9, 16))
44
+
45
+ >>> instant(Period((DateUnit.YEAR, Instant((2021, 9, 16)), 1)))
46
+ Instant((2021, 9, 16))
47
+
48
+ >>> instant(2021)
49
+ Instant((2021, 1, 1))
50
+
51
+ >>> instant("2021")
52
+ Instant((2021, 1, 1))
53
+
54
+ >>> instant([2021])
55
+ Instant((2021, 1, 1))
56
+
57
+ >>> instant([2021, 9])
58
+ Instant((2021, 9, 1))
59
+
60
+ >>> instant(None)
61
+ Traceback (most recent call last):
62
+ openfisca_core.periods._errors.InstantError: 'None' is not a valid i...
63
+
64
+ """
65
+
66
+ if isinstance(value, t.SeqInt):
67
+ return Instant((list(value) + [1] * 3)[:3])
68
+
69
+ raise InstantError(str(value))
70
+
71
+
72
+ @instant.register
73
+ def _(value: None) -> NoReturn:
74
+ raise InstantError(str(value))
75
+
76
+
77
+ @instant.register
78
+ def _(value: int) -> t.Instant:
79
+ return Instant((value, 1, 1))
80
+
81
+
82
+ @instant.register
83
+ def _(value: Period) -> t.Instant:
84
+ return value.start
85
+
86
+
87
+ @instant.register
88
+ def _(value: t.Instant) -> t.Instant:
89
+ return value
90
+
91
+
92
+ @instant.register
93
+ def _(value: datetime.date) -> t.Instant:
94
+ return Instant((value.year, value.month, value.day))
95
+
96
+
97
+ @instant.register
98
+ def _(value: str) -> t.Instant:
99
+ return parse_instant(value)
100
+
101
+
102
+ def instant_date(instant: None | t.Instant) -> None | datetime.date:
103
+ """Returns the date representation of an ``Instant``.
104
+
105
+ Args:
106
+ instant: An ``Instant``.
107
+
108
+ Returns:
109
+ None: When ``instant`` is None.
110
+ datetime.date: Otherwise.
111
+
112
+ Examples:
113
+ >>> instant_date(Instant((2021, 1, 1)))
114
+ Date(2021, 1, 1)
115
+
116
+ """
117
+ if instant is None:
118
+ return None
119
+
120
+ instant_date = config.date_by_instant_cache.get(instant)
121
+
122
+ if instant_date is None:
123
+ config.date_by_instant_cache[instant] = instant_date = pendulum.date(*instant)
124
+
125
+ return instant_date
126
+
127
+
128
+ @functools.singledispatch
129
+ def period(value: object) -> t.Period:
130
+ """Build a new period, aka a triple (unit, start_instant, size).
131
+
132
+ Args:
133
+ value: A ``period-like`` object.
134
+
135
+ Returns:
136
+ :obj:`.Period`: A period.
137
+
138
+ Raises:
139
+ :exc:`ValueError`: When the arguments were invalid, like "2021-32-13".
140
+
141
+ Examples:
142
+ >>> period(Period((DateUnit.YEAR, Instant((2021, 1, 1)), 1)))
143
+ Period((<DateUnit.YEAR: 'year'>, Instant((2021, 1, 1)), 1))
144
+
145
+ >>> period(Instant((2021, 1, 1)))
146
+ Period((<DateUnit.DAY: 'day'>, Instant((2021, 1, 1)), 1))
147
+
148
+ >>> period(DateUnit.ETERNITY)
149
+ Period((<DateUnit.ETERNITY: 'eternity'>, Instant((-1, -1, -1)), -1))
150
+
151
+ >>> period(2021)
152
+ Period((<DateUnit.YEAR: 'year'>, Instant((2021, 1, 1)), 1))
153
+
154
+ >>> period("2014")
155
+ Period((<DateUnit.YEAR: 'year'>, Instant((2014, 1, 1)), 1))
156
+
157
+ >>> period("year:2014")
158
+ Period((<DateUnit.YEAR: 'year'>, Instant((2014, 1, 1)), 1))
159
+
160
+ >>> period("month:2014-02")
161
+ Period((<DateUnit.MONTH: 'month'>, Instant((2014, 2, 1)), 1))
162
+
163
+ >>> period("year:2014-02")
164
+ Period((<DateUnit.YEAR: 'year'>, Instant((2014, 2, 1)), 1))
165
+
166
+ >>> period("day:2014-02-02")
167
+ Period((<DateUnit.DAY: 'day'>, Instant((2014, 2, 2)), 1))
168
+
169
+ >>> period("day:2014-02-02:3")
170
+ Period((<DateUnit.DAY: 'day'>, Instant((2014, 2, 2)), 3))
171
+
172
+ """
173
+
174
+ one, two, three = 1, 2, 3
175
+
176
+ # We return an "eternity-period", for example
177
+ # ``<Period(('eternity', <Instant(-1, -1, -1)>, -1))>``.
178
+ if str(value).lower() == DateUnit.ETERNITY:
179
+ return Period.eternity()
180
+
181
+ # We try to parse from an ISO format/calendar period.
182
+ if isinstance(value, t.InstantStr):
183
+ return parse_period(value)
184
+
185
+ # A complex period has a ':' in its string.
186
+ if isinstance(value, t.PeriodStr):
187
+ components = value.split(":")
188
+
189
+ # The left-most component must be a valid unit
190
+ unit = components[0]
191
+
192
+ if unit not in list(DateUnit) or unit == DateUnit.ETERNITY:
193
+ raise PeriodError(str(value))
194
+
195
+ # Cast ``unit`` to DateUnit.
196
+ unit = DateUnit(unit)
197
+
198
+ # The middle component must be a valid iso period
199
+ period = parse_period(components[1])
200
+
201
+ # Periods like year:2015-03 have a size of 1
202
+ if len(components) == two:
203
+ size = one
204
+
205
+ # if provided, make sure the size is an integer
206
+ elif len(components) == three:
207
+ try:
208
+ size = int(components[2])
209
+
210
+ except ValueError as error:
211
+ raise PeriodError(str(value)) from error
212
+
213
+ # If there are more than 2 ":" in the string, the period is invalid
214
+ else:
215
+ raise PeriodError(str(value))
216
+
217
+ # Reject ambiguous periods such as month:2014
218
+ if unit_weight(period.unit) > unit_weight(unit):
219
+ raise PeriodError(str(value))
220
+
221
+ return Period((unit, period.start, size))
222
+
223
+ raise PeriodError(str(value))
224
+
225
+
226
+ @period.register
227
+ def _(value: None) -> NoReturn:
228
+ raise PeriodError(str(value))
229
+
230
+
231
+ @period.register
232
+ def _(value: int) -> t.Period:
233
+ return Period((DateUnit.YEAR, instant(value), 1))
234
+
235
+
236
+ @period.register
237
+ def _(value: t.Period) -> t.Period:
238
+ return value
239
+
240
+
241
+ @period.register
242
+ def _(value: t.Instant) -> t.Period:
243
+ return Period((DateUnit.DAY, value, 1))
244
+
245
+
246
+ @period.register
247
+ def _(value: datetime.date) -> t.Period:
248
+ return Period((DateUnit.DAY, instant(value), 1))
249
+
250
+
251
+ def key_period_size(period: t.Period) -> str:
252
+ """Define a key in order to sort periods by length.
253
+
254
+ It uses two aspects: first, ``unit``, then, ``size``.
255
+
256
+ Args:
257
+ period: An :mod:`.openfisca_core` :obj:`.Period`.
258
+
259
+ Returns:
260
+ :obj:`str`: A string.
261
+
262
+ Examples:
263
+ >>> instant = Instant((2021, 9, 14))
264
+
265
+ >>> period = Period((DateUnit.DAY, instant, 1))
266
+ >>> key_period_size(period)
267
+ '100_1'
268
+
269
+ >>> period = Period((DateUnit.YEAR, instant, 3))
270
+ >>> key_period_size(period)
271
+ '300_3'
272
+
273
+ """
274
+
275
+ return f"{unit_weight(period.unit)}_{period.size}"
276
+
277
+
278
+ def unit_weights() -> dict[t.DateUnit, int]:
279
+ """Assign weights to date units.
280
+
281
+ Examples:
282
+ >>> unit_weights()
283
+ {<DateUnit.WEEKDAY: 'weekday'>: 100, ...ETERNITY: 'eternity'>: 400}
284
+
285
+ """
286
+ return {
287
+ DateUnit.WEEKDAY: 100,
288
+ DateUnit.WEEK: 200,
289
+ DateUnit.DAY: 100,
290
+ DateUnit.MONTH: 200,
291
+ DateUnit.YEAR: 300,
292
+ DateUnit.ETERNITY: 400,
293
+ }
294
+
295
+
296
+ def unit_weight(unit: t.DateUnit) -> int:
297
+ """Retrieves a specific date unit weight.
298
+
299
+ Examples:
300
+ >>> unit_weight(DateUnit.DAY)
301
+ 100
302
+
303
+ """
304
+ return unit_weights()[unit]
305
+
306
+
307
+ __all__ = [
308
+ "instant",
309
+ "instant_date",
310
+ "key_period_size",
311
+ "period",
312
+ "unit_weight",
313
+ "unit_weights",
314
+ ]
@@ -0,0 +1,226 @@
1
+ from __future__ import annotations
2
+
3
+ import pendulum
4
+
5
+ from openfisca_core import types as t
6
+
7
+ from . import config
8
+ from .date_unit import DateUnit
9
+
10
+
11
+ class Instant(tuple[int, int, int]):
12
+ """An instant in time (year, month, day).
13
+
14
+ An :class:`.Instant` represents the most atomic and indivisible
15
+ legislation's date unit.
16
+
17
+ Current implementation considers this unit to be a day, so
18
+ :obj:`instants <.Instant>` can be thought of as "day dates".
19
+
20
+ Examples:
21
+ >>> instant = Instant((2021, 9, 13))
22
+
23
+ >>> repr(Instant)
24
+ "<class 'openfisca_core.periods.instant_.Instant'>"
25
+
26
+ >>> repr(instant)
27
+ 'Instant((2021, 9, 13))'
28
+
29
+ >>> str(instant)
30
+ '2021-09-13'
31
+
32
+ >>> dict([(instant, (2021, 9, 13))])
33
+ {Instant((2021, 9, 13)): (2021, 9, 13)}
34
+
35
+ >>> list(instant)
36
+ [2021, 9, 13]
37
+
38
+ >>> instant[0]
39
+ 2021
40
+
41
+ >>> instant[0] in instant
42
+ True
43
+
44
+ >>> len(instant)
45
+ 3
46
+
47
+ >>> instant == (2021, 9, 13)
48
+ True
49
+
50
+ >>> instant != (2021, 9, 13)
51
+ False
52
+
53
+ >>> instant > (2020, 9, 13)
54
+ True
55
+
56
+ >>> instant < (2020, 9, 13)
57
+ False
58
+
59
+ >>> instant >= (2020, 9, 13)
60
+ True
61
+
62
+ >>> instant <= (2020, 9, 13)
63
+ False
64
+
65
+ >>> instant.year
66
+ 2021
67
+
68
+ >>> instant.month
69
+ 9
70
+
71
+ >>> instant.day
72
+ 13
73
+
74
+ >>> instant.date
75
+ Date(2021, 9, 13)
76
+
77
+ >>> year, month, day = instant
78
+
79
+ """
80
+
81
+ __slots__ = ()
82
+
83
+ def __repr__(self) -> str:
84
+ return f"{self.__class__.__name__}({super().__repr__()})"
85
+
86
+ def __str__(self) -> t.InstantStr:
87
+ instant_str = config.str_by_instant_cache.get(self)
88
+
89
+ if instant_str is None:
90
+ instant_str = t.InstantStr(self.date.isoformat())
91
+ config.str_by_instant_cache[self] = instant_str
92
+
93
+ return instant_str
94
+
95
+ def __lt__(self, other: object) -> bool:
96
+ if isinstance(other, Instant):
97
+ return super().__lt__(other)
98
+ return NotImplemented
99
+
100
+ def __le__(self, other: object) -> bool:
101
+ if isinstance(other, Instant):
102
+ return super().__le__(other)
103
+ return NotImplemented
104
+
105
+ @property
106
+ def date(self) -> pendulum.Date:
107
+ instant_date = config.date_by_instant_cache.get(self)
108
+
109
+ if instant_date is None:
110
+ instant_date = pendulum.date(*self)
111
+ config.date_by_instant_cache[self] = instant_date
112
+
113
+ return instant_date
114
+
115
+ @property
116
+ def day(self) -> int:
117
+ return self[2]
118
+
119
+ @property
120
+ def month(self) -> int:
121
+ return self[1]
122
+
123
+ @property
124
+ def year(self) -> int:
125
+ return self[0]
126
+
127
+ @property
128
+ def is_eternal(self) -> bool:
129
+ return self == self.eternity()
130
+
131
+ def offset(self, offset: str | int, unit: t.DateUnit) -> t.Instant | None:
132
+ """Increments/decrements the given instant with offset units.
133
+
134
+ Args:
135
+ offset: How much of ``unit`` to offset.
136
+ unit: What to offset
137
+
138
+ Returns:
139
+ :obj:`.Instant`: A new :obj:`.Instant` in time.
140
+
141
+ Raises:
142
+ :exc:`AssertionError`: When ``unit`` is not a date unit.
143
+ :exc:`AssertionError`: When ``offset`` is not either ``first-of``,
144
+ ``last-of``, or any :obj:`int`.
145
+
146
+ Examples:
147
+ >>> Instant((2020, 12, 31)).offset("first-of", DateUnit.MONTH)
148
+ Instant((2020, 12, 1))
149
+
150
+ >>> Instant((2020, 1, 1)).offset("last-of", DateUnit.YEAR)
151
+ Instant((2020, 12, 31))
152
+
153
+ >>> Instant((2020, 1, 1)).offset(1, DateUnit.YEAR)
154
+ Instant((2021, 1, 1))
155
+
156
+ >>> Instant((2020, 1, 1)).offset(-3, DateUnit.DAY)
157
+ Instant((2019, 12, 29))
158
+
159
+ """
160
+ year, month, _ = self
161
+
162
+ assert unit in (DateUnit.isoformat + DateUnit.isocalendar), (
163
+ f"Invalid unit: {unit} of type {type(unit)}"
164
+ )
165
+
166
+ if offset == "first-of":
167
+ if unit == DateUnit.YEAR:
168
+ return self.__class__((year, 1, 1))
169
+
170
+ if unit == DateUnit.MONTH:
171
+ return self.__class__((year, month, 1))
172
+
173
+ if unit == DateUnit.WEEK:
174
+ date = self.date
175
+ date = date.start_of("week")
176
+ return self.__class__((date.year, date.month, date.day))
177
+ return None
178
+
179
+ if offset == "last-of":
180
+ if unit == DateUnit.YEAR:
181
+ return self.__class__((year, 12, 31))
182
+
183
+ if unit == DateUnit.MONTH:
184
+ date = self.date
185
+ date = date.end_of("month")
186
+ return self.__class__((date.year, date.month, date.day))
187
+
188
+ if unit == DateUnit.WEEK:
189
+ date = self.date
190
+ date = date.end_of("week")
191
+ return self.__class__((date.year, date.month, date.day))
192
+ return None
193
+
194
+ assert isinstance(
195
+ offset,
196
+ int,
197
+ ), f"Invalid offset: {offset} of type {type(offset)}"
198
+
199
+ if unit == DateUnit.YEAR:
200
+ date = self.date
201
+ date = date.add(years=offset)
202
+ return self.__class__((date.year, date.month, date.day))
203
+
204
+ if unit == DateUnit.MONTH:
205
+ date = self.date
206
+ date = date.add(months=offset)
207
+ return self.__class__((date.year, date.month, date.day))
208
+
209
+ if unit == DateUnit.WEEK:
210
+ date = self.date
211
+ date = date.add(weeks=offset)
212
+ return self.__class__((date.year, date.month, date.day))
213
+
214
+ if unit in (DateUnit.DAY, DateUnit.WEEKDAY):
215
+ date = self.date
216
+ date = date.add(days=offset)
217
+ return self.__class__((date.year, date.month, date.day))
218
+ return None
219
+
220
+ @classmethod
221
+ def eternity(cls) -> t.Instant:
222
+ """Return an eternity instant."""
223
+ return cls((-1, -1, -1))
224
+
225
+
226
+ __all__ = ["Instant"]