dycw-utilities 0.168.2__tar.gz → 0.168.4__tar.gz

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.

Potentially problematic release.


This version of dycw-utilities might be problematic. Click here for more details.

Files changed (221) hide show
  1. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/PKG-INFO +4 -4
  2. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/pyproject.toml +11 -15
  3. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_pydantic_settings.py +106 -0
  4. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/__init__.py +1 -1
  5. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/pydantic_settings.py +65 -6
  6. dycw_utilities-0.168.2/src/tests/test_typed_settings.py +0 -321
  7. dycw_utilities-0.168.2/src/utilities/typed_settings.py +0 -152
  8. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/.gitignore +0 -0
  9. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/LICENSE +0 -0
  10. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/README.md +0 -0
  11. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/__init__.py +0 -0
  12. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/conftest.py +0 -0
  13. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/modules/__init__.py +0 -0
  14. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/modules/package_missing/__init__.py +0 -0
  15. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/modules/package_missing/module.py +0 -0
  16. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/modules/package_with/__init__.py +0 -0
  17. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/modules/package_with/outer_1.py +0 -0
  18. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/modules/package_with/outer_2.py +0 -0
  19. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/modules/package_with/subpackage/__init__.py +0 -0
  20. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/modules/package_with/subpackage/inner_1.py +0 -0
  21. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/modules/package_with/subpackage/inner_2.py +0 -0
  22. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/modules/package_with/subpackage/inner_3.py +0 -0
  23. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/modules/package_without/__init__.py +0 -0
  24. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/modules/package_without/module_1.py +0 -0
  25. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/modules/package_without/module_2.py +0 -0
  26. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/modules/standalone.py +0 -0
  27. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/modules/with_imports.py +0 -0
  28. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -0
  29. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -0
  30. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -0
  31. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -0
  32. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -0
  33. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -0
  34. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -0
  35. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -0
  36. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_aeventkit.py +0 -0
  37. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_altair.py +0 -0
  38. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_asyncio.py +0 -0
  39. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_atomicwrites.py +0 -0
  40. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_atools.py +0 -0
  41. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_cachetools.py +0 -0
  42. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_click.py +0 -0
  43. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_concurrent.py +0 -0
  44. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_contextlib.py +0 -0
  45. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_contextvars.py +0 -0
  46. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_cryptography.py +0 -0
  47. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_cvxpy.py +0 -0
  48. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_dataclasses.py +0 -0
  49. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_docker.py +0 -0
  50. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_enum.py +0 -0
  51. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_errors.py +0 -0
  52. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_fastapi.py +0 -0
  53. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_fpdf2.py +0 -0
  54. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_functions.py +0 -0
  55. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_functools.py +0 -0
  56. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_getpass.py +0 -0
  57. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_git.py +0 -0
  58. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_gzip.py +0 -0
  59. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_hashlib.py +0 -0
  60. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_http.py +0 -0
  61. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_hypothesis.py +0 -0
  62. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_importlib.py +0 -0
  63. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_inflect.py +0 -0
  64. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_ipython.py +0 -0
  65. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_iterables.py +0 -0
  66. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_jinja2.py +0 -0
  67. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_json.py +0 -0
  68. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_jupyter.py +0 -0
  69. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_libcst.py +0 -0
  70. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_lightweight_charts.py +0 -0
  71. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_logging.py +0 -0
  72. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_math.py +0 -0
  73. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_memory_profiler.py +0 -0
  74. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_modules.py +0 -0
  75. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_more_itertools.py +0 -0
  76. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_numpy.py +0 -0
  77. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_objects/__init__.py +0 -0
  78. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_objects/objects.py +0 -0
  79. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_operator.py +0 -0
  80. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_optuna.py +0 -0
  81. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_orjson.py +0 -0
  82. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_os.py +0 -0
  83. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_parse.py +0 -0
  84. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_pathlib.py +0 -0
  85. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_pickle.py +0 -0
  86. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_platform.py +0 -0
  87. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_polars.py +0 -0
  88. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_polars_ols.py +0 -0
  89. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_postgres.py +0 -0
  90. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_pottery.py +0 -0
  91. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_pqdm.py +0 -0
  92. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_psutil.py +0 -0
  93. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_pydantic_settings_sops.py +0 -0
  94. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_pyinstrument.py +0 -0
  95. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_pytest.py +0 -0
  96. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_pytest_randomly.py +0 -0
  97. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_pytest_regressions.py +0 -0
  98. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_random.py +0 -0
  99. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_re.py +0 -0
  100. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_redis.py +0 -0
  101. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_reprlib.py +0 -0
  102. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_scipy.py +0 -0
  103. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_sentinel.py +0 -0
  104. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_shelve.py +0 -0
  105. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_slack_sdk.py +0 -0
  106. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_socket.py +0 -0
  107. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_sqlalchemy.py +0 -0
  108. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_sqlalchemy_polars.py +0 -0
  109. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_statsmodels.py +0 -0
  110. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_string.py +0 -0
  111. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_tempfile.py +0 -0
  112. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_testbook.py +0 -0
  113. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_text.py +0 -0
  114. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_threading.py +0 -0
  115. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_timer.py +0 -0
  116. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_traceback.py +0 -0
  117. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_types.py +0 -0
  118. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_typing.py +0 -0
  119. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_typing_funcs/__init__.py +0 -0
  120. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_typing_funcs/no_future.py +0 -0
  121. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_typing_funcs/with_future.py +0 -0
  122. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_tzdata.py +0 -0
  123. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_tzlocal.py +0 -0
  124. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_uuid.py +0 -0
  125. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_version.py +0 -0
  126. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_warnings.py +0 -0
  127. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_whenever.py +0 -0
  128. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_zipfile.py +0 -0
  129. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_zoneinfo.py +0 -0
  130. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/aeventkit.py +0 -0
  131. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/altair.py +0 -0
  132. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/asyncio.py +0 -0
  133. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/atomicwrites.py +0 -0
  134. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/atools.py +0 -0
  135. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/cachetools.py +0 -0
  136. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/click.py +0 -0
  137. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/concurrent.py +0 -0
  138. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/contextlib.py +0 -0
  139. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/contextvars.py +0 -0
  140. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/cryptography.py +0 -0
  141. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/cvxpy.py +0 -0
  142. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/dataclasses.py +0 -0
  143. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/docker.py +0 -0
  144. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/enum.py +0 -0
  145. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/errors.py +0 -0
  146. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/fastapi.py +0 -0
  147. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/fpdf2.py +0 -0
  148. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/functions.py +0 -0
  149. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/functools.py +0 -0
  150. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/getpass.py +0 -0
  151. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/git.py +0 -0
  152. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/gzip.py +0 -0
  153. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/hashlib.py +0 -0
  154. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/http.py +0 -0
  155. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/hypothesis.py +0 -0
  156. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/importlib.py +0 -0
  157. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/inflect.py +0 -0
  158. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/ipython.py +0 -0
  159. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/iterables.py +0 -0
  160. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/jinja2.py +0 -0
  161. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/json.py +0 -0
  162. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/jupyter.py +0 -0
  163. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/libcst.py +0 -0
  164. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/lightweight_charts.py +0 -0
  165. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/logging.py +0 -0
  166. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/math.py +0 -0
  167. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/memory_profiler.py +0 -0
  168. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/modules.py +0 -0
  169. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/more_itertools.py +0 -0
  170. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/numpy.py +0 -0
  171. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/operator.py +0 -0
  172. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/optuna.py +0 -0
  173. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/orjson.py +0 -0
  174. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/os.py +0 -0
  175. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/parse.py +0 -0
  176. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/pathlib.py +0 -0
  177. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/pickle.py +0 -0
  178. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/platform.py +0 -0
  179. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/polars.py +0 -0
  180. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/polars_ols.py +0 -0
  181. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/postgres.py +0 -0
  182. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/pottery.py +0 -0
  183. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/pqdm.py +0 -0
  184. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/psutil.py +0 -0
  185. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/py.typed +0 -0
  186. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/pydantic_settings_sops.py +0 -0
  187. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/pyinstrument.py +0 -0
  188. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/pytest.py +0 -0
  189. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/pytest_plugins/__init__.py +0 -0
  190. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/pytest_plugins/pytest_randomly.py +0 -0
  191. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/pytest_plugins/pytest_regressions.py +0 -0
  192. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/pytest_regressions.py +0 -0
  193. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/random.py +0 -0
  194. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/re.py +0 -0
  195. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/redis.py +0 -0
  196. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/reprlib.py +0 -0
  197. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/scipy.py +0 -0
  198. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/sentinel.py +0 -0
  199. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/shelve.py +0 -0
  200. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/slack_sdk.py +0 -0
  201. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/socket.py +0 -0
  202. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/sqlalchemy.py +0 -0
  203. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/sqlalchemy_polars.py +0 -0
  204. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/statsmodels.py +0 -0
  205. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/string.py +0 -0
  206. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/tempfile.py +0 -0
  207. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/testbook.py +0 -0
  208. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/text.py +0 -0
  209. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/threading.py +0 -0
  210. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/timer.py +0 -0
  211. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/traceback.py +0 -0
  212. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/types.py +0 -0
  213. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/typing.py +0 -0
  214. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/tzdata.py +0 -0
  215. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/tzlocal.py +0 -0
  216. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/uuid.py +0 -0
  217. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/version.py +0 -0
  218. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/warnings.py +0 -0
  219. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/whenever.py +0 -0
  220. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/zipfile.py +0 -0
  221. {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/zoneinfo.py +0 -0
@@ -1,22 +1,22 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.168.2
3
+ Version: 0.168.4
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
7
7
  Requires-Dist: atomicwrites<1.5,>=1.4.1
8
8
  Requires-Dist: typing-extensions<4.16,>=4.15.0
9
9
  Requires-Dist: tzlocal<5.4,>=5.3.1
10
- Requires-Dist: whenever<0.10,>=0.9.0
10
+ Requires-Dist: whenever<0.10,>=0.9.2
11
11
  Provides-Extra: logging
12
12
  Requires-Dist: coloredlogs<15.1,>=15.0.1; extra == 'logging'
13
13
  Provides-Extra: test
14
14
  Requires-Dist: dycw-pytest-only<2.2,>=2.1.1; extra == 'test'
15
- Requires-Dist: hypothesis<6.141,>=6.140.2; extra == 'test'
15
+ Requires-Dist: hypothesis<6.141,>=6.140.3; extra == 'test'
16
16
  Requires-Dist: pytest-asyncio<1.3,>=1.2.0; extra == 'test'
17
17
  Requires-Dist: pytest-cov<7.1,>=7.0.0; extra == 'test'
18
18
  Requires-Dist: pytest-instafail<0.6,>=0.5.0; extra == 'test'
19
- Requires-Dist: pytest-lazy-fixtures<1.4,>=1.3.4; extra == 'test'
19
+ Requires-Dist: pytest-lazy-fixtures<1.5,>=1.4.0; extra == 'test'
20
20
  Requires-Dist: pytest-randomly<4.1,>=4.0.1; extra == 'test'
21
21
  Requires-Dist: pytest-regressions<2.9,>=2.8.3; extra == 'test'
22
22
  Requires-Dist: pytest-repeat<0.10,>=0.9.4; extra == 'test'
@@ -31,10 +31,10 @@ core = [
31
31
  "atomicwrites >=1.4.1, <1.5",
32
32
  "typing-extensions >=4.15.0, <4.16",
33
33
  "tzlocal >=5.3.1, <5.4",
34
- "whenever >=0.9.0, <0.10",
34
+ "whenever >=0.9.2, <0.10",
35
35
  ]
36
36
  cryptography = [
37
- "cryptography >=46.0.1, <46.1",
37
+ "cryptography >=46.0.2, <46.1",
38
38
  ]
39
39
  cvxpy = [
40
40
  "cvxpy >=1.7.3, <1.8",
@@ -52,7 +52,7 @@ dev = [
52
52
  "pytest-timeout >=2.4.0, <2.5",
53
53
  ]
54
54
  fastapi = [
55
- "fastapi >=0.117.1, <0.118",
55
+ "fastapi >=0.118.0, <0.119",
56
56
  ]
57
57
  fastapi-test = [
58
58
  "httpx",
@@ -71,7 +71,7 @@ http-test = [
71
71
  "orjson",
72
72
  ]
73
73
  hypothesis = [
74
- "hypothesis >=6.140.2, <6.141",
74
+ "hypothesis >=6.140.3, <6.141",
75
75
  ]
76
76
  hypothesis-test = [
77
77
  "libcst",
@@ -124,7 +124,7 @@ orjson-test = [
124
124
  "polars",
125
125
  ]
126
126
  polars = [
127
- "polars >=1.33.1, <1.34",
127
+ "polars >=1.34.0, <1.35",
128
128
  ]
129
129
  polars-ols = [
130
130
  "polars-ols >=0.3.5, <0.4",
@@ -198,7 +198,7 @@ sklearn = [
198
198
  "scikit-learn >=1.7.2, <1.8",
199
199
  ]
200
200
  slack-sdk = [
201
- "slack-sdk >=3.36.0, <3.37",
201
+ "slack-sdk >=3.37.0, <3.38",
202
202
  ]
203
203
  slack-sdk-test = [
204
204
  "aiohttp",
@@ -227,9 +227,6 @@ statsmodels = [
227
227
  testbook = [
228
228
  "testbook >=0.4.2, <0.5",
229
229
  ]
230
- typed-settings = [
231
- "typed-settings >=25.0.0, <25.1",
232
- ]
233
230
  tzdata = [
234
231
  "tzdata >=2025.2, <2025.3",
235
232
  ]
@@ -244,12 +241,12 @@ dependencies = [
244
241
  "atomicwrites >=1.4.1, <1.5",
245
242
  "typing-extensions >=4.15.0, <4.16",
246
243
  "tzlocal >=5.3.1, <5.4",
247
- "whenever >=0.9.0, <0.10",
244
+ "whenever >=0.9.2, <0.10",
248
245
  ]
249
246
  name = "dycw-utilities"
250
247
  readme = "README.md"
251
248
  requires-python = ">= 3.12"
252
- version = "0.168.2"
249
+ version = "0.168.4"
253
250
 
254
251
  [project.entry-points.pytest11]
255
252
  pytest-randomly = "utilities.pytest_plugins.pytest_randomly"
@@ -261,12 +258,12 @@ logging = [
261
258
  ]
262
259
  test = [
263
260
  "dycw-pytest-only >=2.1.1, <2.2",
264
- "hypothesis >=6.140.2, <6.141",
261
+ "hypothesis >=6.140.3, <6.141",
265
262
  "pytest >=8.4.2, <8.5",
266
263
  "pytest-asyncio >=1.2.0, <1.3",
267
264
  "pytest-cov >=7.0.0, <7.1",
268
265
  "pytest-instafail >=0.5.0, <0.6",
269
- "pytest-lazy-fixtures >=1.3.4, <1.4",
266
+ "pytest-lazy-fixtures >=1.4.0, <1.5",
270
267
  "pytest-randomly >=4.0.1, <4.1",
271
268
  "pytest-regressions >=2.8.3, <2.9",
272
269
  "pytest-repeat >=0.9.4, <0.10",
@@ -282,7 +279,7 @@ test = [
282
279
  # bump-my-version
283
280
  [tool.bumpversion]
284
281
  allow_dirty = true
285
- current_version = "0.168.2"
282
+ current_version = "0.168.4"
286
283
 
287
284
  [[tool.bumpversion.files]]
288
285
  filename = "src/utilities/__init__.py"
@@ -486,7 +483,6 @@ select = [
486
483
  "S101", # assert
487
484
  "SLF001", # private-member-access
488
485
  ]
489
- "src/tests/test_typed_settings.py" = ["I002"] # missing-required-import
490
486
  "src/tests/test_typing_funcs/no_future.py" = ["I002"] # missing-required-import
491
487
 
492
488
  [tool.ruff.lint.flake8-tidy-imports]
@@ -1,6 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import json
4
+ from stat import S_IXUSR
5
+ from subprocess import STDOUT, CalledProcessError, check_output
4
6
  from typing import TYPE_CHECKING, ClassVar
5
7
 
6
8
  import tomlkit
@@ -8,6 +10,7 @@ import yaml
8
10
  from pydantic_settings import BaseSettings, SettingsConfigDict
9
11
  from pytest import mark, param
10
12
 
13
+ from tests.conftest import SKIPIF_CI_AND_WINDOWS
11
14
  from utilities.os import temp_environ
12
15
  from utilities.pydantic_settings import (
13
16
  CustomBaseSettings,
@@ -146,3 +149,106 @@ class TestHashableBaseSettings:
146
149
 
147
150
  settings = load_settings(Settings)
148
151
  _ = hash(settings)
152
+
153
+
154
+ class TestLoadSettings:
155
+ @mark.parametrize(
156
+ ("args", "expected"),
157
+ [
158
+ param([], "settings=_Settings(a=1, b=2, inner=_Inner(c=3, d=4))"),
159
+ param(["-a", "5"], "settings=_Settings(a=5, b=2, inner=_Inner(c=3, d=4))"),
160
+ param(
161
+ ["--inner.c", "5"],
162
+ "settings=_Settings(a=1, b=2, inner=_Inner(c=5, d=4))",
163
+ ),
164
+ param(
165
+ ["-h"],
166
+ """
167
+ usage: script.py [-h] [-a int] [-b int] [--inner [JSON]] [--inner.c int]
168
+ [--inner.d int]
169
+
170
+ options:
171
+ -h, --help show this help message and exit
172
+ -a int (default: 1)
173
+ -b int (default: 2)
174
+
175
+ inner options:
176
+ --inner [JSON] set inner from JSON string (default: {})
177
+ --inner.c int (default: 3)
178
+ --inner.d int (default: 4)
179
+ """,
180
+ ),
181
+ ],
182
+ )
183
+ @SKIPIF_CI_AND_WINDOWS
184
+ def test_cli(self, *, tmp_path: Path, args: list[str], expected: str) -> None:
185
+ script = tmp_path.joinpath("script.py")
186
+ _ = script.write_text("""\
187
+ #!/usr/bin/env python3
188
+ from __future__ import annotations
189
+
190
+ from collections.abc import Sequence
191
+ from pathlib import Path
192
+ from typing import ClassVar
193
+
194
+ from pydantic_settings import BaseSettings
195
+
196
+ from utilities.pydantic_settings import CustomBaseSettings, PathLikeOrWithSection, load_settings
197
+
198
+ class _Settings(CustomBaseSettings):
199
+ toml_files: ClassVar[Sequence[PathLikeOrWithSection]] = [
200
+ Path(__file__).parent.joinpath("config.toml")
201
+ ]
202
+
203
+ a: int
204
+ b: int
205
+ inner: _Inner
206
+
207
+ class _Inner(BaseSettings):
208
+ c: int
209
+ d: int
210
+
211
+ def main() -> None:
212
+ settings = load_settings(_Settings, cli=True)
213
+ print(f"{settings=}")
214
+
215
+ if __name__ == "__main__":
216
+ main()
217
+ """)
218
+ script.chmod(script.stat().st_mode | S_IXUSR)
219
+ config = tmp_path.joinpath("config.toml")
220
+ _ = config.write_text(
221
+ """\
222
+ a = 1
223
+ b = 2
224
+
225
+ [inner]
226
+ c = 3
227
+ d = 4
228
+ """
229
+ )
230
+ try:
231
+ result = check_output([script, *args], stderr=STDOUT, text=True).strip("\n")
232
+ except CalledProcessError as error:
233
+ raise RuntimeError(error.stdout) from None
234
+ assert result == expected.strip("\n")
235
+
236
+ def test_cli_coverage(self, *, tmp_path: Path) -> None:
237
+ config = tmp_path.joinpath("config.toml")
238
+ _ = config.write_text("""
239
+ a = 1
240
+
241
+ [inner]
242
+ b = 2""")
243
+
244
+ class Example(CustomBaseSettings):
245
+ toml_files: ClassVar[Sequence[PathLikeOrWithSection]] = [config]
246
+
247
+ a: int
248
+ inner: _Inner
249
+
250
+ class _Inner(BaseSettings):
251
+ b: int
252
+
253
+ _ = Example.model_rebuild()
254
+ _ = load_settings(Example, cli=True)
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.168.2"
3
+ __version__ = "0.168.4"
@@ -2,10 +2,12 @@ from __future__ import annotations
2
2
 
3
3
  from functools import reduce
4
4
  from pathlib import Path
5
- from typing import TYPE_CHECKING, Any, ClassVar, assert_never, override
5
+ from typing import TYPE_CHECKING, Any, ClassVar, assert_never, cast, override
6
6
 
7
+ from pydantic import Field, create_model
7
8
  from pydantic_settings import (
8
9
  BaseSettings,
10
+ CliSettingsSource,
9
11
  JsonConfigSettingsSource,
10
12
  PydanticBaseSettingsSource,
11
13
  SettingsConfigDict,
@@ -14,6 +16,7 @@ from pydantic_settings import (
14
16
  )
15
17
  from pydantic_settings.sources import DEFAULT_PATH
16
18
 
19
+ from utilities.errors import ImpossibleCaseError
17
20
  from utilities.iterables import always_iterable
18
21
 
19
22
  if TYPE_CHECKING:
@@ -76,11 +79,6 @@ class CustomBaseSettings(BaseSettings):
76
79
  )
77
80
 
78
81
 
79
- def load_settings[T: BaseSettings](cls: type[T], /) -> T:
80
- """Load a set of settings."""
81
- return cls()
82
-
83
-
84
82
  class JsonConfigSectionSettingsSource(JsonConfigSettingsSource):
85
83
  @override
86
84
  def __init__(
@@ -168,6 +166,67 @@ class HashableBaseSettings(BaseSettings):
168
166
  model_config: ClassVar[SettingsConfigDict] = SettingsConfigDict(frozen=True)
169
167
 
170
168
 
169
+ ##
170
+
171
+
172
+ def load_settings[T: BaseSettings](cls: type[T], /, *, cli: bool = False) -> T:
173
+ """Load a set of settings."""
174
+ _ = cls.model_rebuild()
175
+ if cli:
176
+ cls_with_defaults = _load_settings_create_model(cls)
177
+
178
+ @classmethod
179
+ def settings_customise_sources(
180
+ cls: type[BaseSettings],
181
+ settings_cls: type[BaseSettings],
182
+ init_settings: PydanticBaseSettingsSource,
183
+ env_settings: PydanticBaseSettingsSource,
184
+ dotenv_settings: PydanticBaseSettingsSource,
185
+ file_secret_settings: PydanticBaseSettingsSource,
186
+ ) -> tuple[PydanticBaseSettingsSource, ...]:
187
+ parent = cast(
188
+ "Any", super(cls_with_defaults, cls)
189
+ ).settings_customise_sources(
190
+ settings_cls=settings_cls,
191
+ init_settings=init_settings,
192
+ env_settings=env_settings,
193
+ dotenv_settings=dotenv_settings,
194
+ file_secret_settings=file_secret_settings,
195
+ )
196
+ return (
197
+ CliSettingsSource(
198
+ settings_cls, cli_parse_args=True, case_sensitive=False
199
+ ),
200
+ *parent,
201
+ )
202
+
203
+ cls_use = type(
204
+ cls.__name__,
205
+ (cls_with_defaults,),
206
+ {"settings_customise_sources": settings_customise_sources},
207
+ )
208
+ cls_use = cast("type[T]", cls_use)
209
+ else:
210
+ cls_use = cls
211
+ return cls_use()
212
+
213
+
214
+ def _load_settings_create_model[T: BaseSettings](
215
+ cls: type[T], /, *, values: T | None = None
216
+ ) -> type[T]:
217
+ values_use = cls() if values is None else values
218
+ kwargs: dict[str, Any] = {}
219
+ for name, field in cls.model_fields.items():
220
+ if (ann := field.annotation) is None:
221
+ raise ImpossibleCaseError(case=[f"{ann=}"]) # pragma: no cover
222
+ value = getattr(values_use, name)
223
+ if issubclass(ann, BaseSettings):
224
+ kwargs[name] = _load_settings_create_model(ann, values=value)
225
+ else:
226
+ kwargs[name] = (field.annotation, Field(default=value))
227
+ return create_model(cls.__name__, __base__=cls, **kwargs)
228
+
229
+
171
230
  __all__ = [
172
231
  "CustomBaseSettings",
173
232
  "HashableBaseSettings",
@@ -1,321 +0,0 @@
1
- from collections.abc import Callable
2
- from dataclasses import dataclass
3
- from ipaddress import IPv4Address, IPv6Address
4
- from pathlib import Path
5
- from typing import Any, ClassVar, Self, assert_never, override
6
- from uuid import UUID
7
-
8
- import typed_settings
9
- from hypothesis import given
10
- from hypothesis.strategies import (
11
- DataObject,
12
- SearchStrategy,
13
- booleans,
14
- data,
15
- integers,
16
- ip_addresses,
17
- sampled_from,
18
- tuples,
19
- uuids,
20
- )
21
- from pytest import mark, param, raises
22
- from typed_settings import EnvLoader, FileLoader, TomlFormat
23
- from whenever import (
24
- Date,
25
- DateDelta,
26
- DateTimeDelta,
27
- MonthDay,
28
- PlainDateTime,
29
- Time,
30
- TimeDelta,
31
- YearMonth,
32
- ZonedDateTime,
33
- )
34
-
35
- from utilities.hypothesis import (
36
- date_deltas,
37
- date_time_deltas,
38
- dates,
39
- month_days,
40
- paths,
41
- plain_date_times,
42
- temp_paths,
43
- text_ascii,
44
- time_deltas,
45
- times,
46
- year_months,
47
- zoned_date_times,
48
- )
49
- from utilities.os import temp_environ
50
- from utilities.re import extract_group
51
- from utilities.sentinel import Sentinel, sentinel
52
- from utilities.text import strip_and_dedent
53
- from utilities.typed_settings import (
54
- ExtendedTSConverter,
55
- LoadSettingsError,
56
- load_settings,
57
- )
58
-
59
- app_names = text_ascii(min_size=1).map(str.lower)
60
-
61
-
62
- @dataclass(kw_only=True, slots=True)
63
- class _Case[T]:
64
- cls: type[T]
65
- strategy: SearchStrategy[T]
66
- serialize: Callable[[T], str]
67
-
68
-
69
- class TestExtendedTSConverter:
70
- cases: ClassVar[list[_Case]] = [
71
- _Case(cls=Date, strategy=dates(), serialize=str),
72
- _Case(cls=DateDelta, strategy=date_deltas(parsable=True), serialize=str),
73
- _Case(
74
- cls=DateTimeDelta, strategy=date_time_deltas(parsable=True), serialize=str
75
- ),
76
- _Case(cls=IPv4Address, strategy=ip_addresses(v=4), serialize=str),
77
- _Case(cls=IPv6Address, strategy=ip_addresses(v=6), serialize=str),
78
- _Case(cls=MonthDay, strategy=month_days(), serialize=str),
79
- _Case(cls=PlainDateTime, strategy=plain_date_times(), serialize=str),
80
- _Case(cls=Time, strategy=times(), serialize=str),
81
- _Case(cls=TimeDelta, strategy=time_deltas(), serialize=str),
82
- _Case(cls=UUID, strategy=uuids(), serialize=str),
83
- _Case(cls=YearMonth, strategy=year_months(), serialize=str),
84
- _Case(cls=ZonedDateTime, strategy=zoned_date_times(), serialize=str),
85
- ]
86
-
87
- @given(data=data())
88
- @mark.parametrize(("cls", "strategy"), [param(c.cls, c.strategy) for c in cases])
89
- def test_default(
90
- self, *, data: DataObject, cls: type[Any], strategy: SearchStrategy[Any]
91
- ) -> None:
92
- default = data.draw(strategy)
93
-
94
- @dataclass(frozen=True, kw_only=True, slots=True)
95
- class Settings:
96
- value: cls = default # pyright: ignore[reportInvalidTypeForm]
97
-
98
- loaded = typed_settings.load_settings(
99
- Settings, loaders=[], converter=ExtendedTSConverter()
100
- )
101
- assert loaded.value == default
102
-
103
- @given(data=data(), root=temp_paths(), app_name=app_names)
104
- @mark.parametrize(
105
- ("cls", "strategy", "serialize"),
106
- [param(c.cls, c.strategy, c.serialize) for c in cases],
107
- )
108
- def test_loaded(
109
- self,
110
- *,
111
- data: DataObject,
112
- root: Path,
113
- app_name: str,
114
- cls: type[Any],
115
- strategy: SearchStrategy[Any],
116
- serialize: Callable[[Any], str],
117
- ) -> None:
118
- default, value = data.draw(tuples(strategy, strategy))
119
-
120
- @dataclass(frozen=True, kw_only=True, slots=True)
121
- class Settings:
122
- value: cls = default # pyright: ignore[reportInvalidTypeForm]
123
-
124
- file = Path(root, "file.toml")
125
- _ = file.write_text(
126
- strip_and_dedent(f"""
127
- [{app_name}]
128
- value = '{serialize(value)}'
129
- """)
130
- )
131
- loaded = typed_settings.load_settings(
132
- Settings,
133
- loaders=[
134
- FileLoader(formats={"*.toml": TomlFormat(app_name)}, files=[file])
135
- ],
136
- converter=ExtendedTSConverter(),
137
- )
138
- assert loaded.value == value
139
-
140
- @given(
141
- root=temp_paths(),
142
- app_name=app_names,
143
- env_name=text_ascii(min_size=1).map(lambda text: f"TEST_{text}".upper()),
144
- env_value=text_ascii(min_size=1),
145
- )
146
- def test_path_env_var(
147
- self, *, root: str, app_name: str, env_name: str, env_value: str
148
- ) -> None:
149
- @dataclass(frozen=True, kw_only=True, slots=True)
150
- class Settings:
151
- value: Path
152
-
153
- file = Path(root, "file.toml")
154
- _ = file.write_text(
155
- strip_and_dedent(f"""
156
- [{app_name}]
157
- value = '${env_name}'
158
- """)
159
- )
160
- with temp_environ({env_name: env_value}):
161
- settings = typed_settings.load_settings(
162
- Settings,
163
- loaders=[
164
- FileLoader(formats={"*.toml": TomlFormat(app_name)}, files=[file])
165
- ],
166
- converter=ExtendedTSConverter(resolve_paths=False),
167
- )
168
- expected = Path(env_value)
169
- assert settings.value == expected
170
-
171
- @given(root=temp_paths(), app_name=app_names, path=paths(), resolve=booleans())
172
- def test_path_resolution(
173
- self, *, root: str, app_name: str, path: Path, resolve: bool
174
- ) -> None:
175
- @dataclass(frozen=True, kw_only=True, slots=True)
176
- class Settings:
177
- value: Path
178
-
179
- file = Path(root, "file.toml")
180
- _ = file.write_text(
181
- strip_and_dedent(f"""
182
- [{app_name}]
183
- value = '{path!s}'
184
- """)
185
- )
186
- settings = typed_settings.load_settings(
187
- Settings,
188
- loaders=[
189
- FileLoader(formats={"*.toml": TomlFormat(app_name)}, files=[file])
190
- ],
191
- converter=ExtendedTSConverter(resolve_paths=resolve),
192
- )
193
- match resolve:
194
- case True:
195
- expected = Path.cwd().joinpath(path)
196
- case False:
197
- expected = Path(path)
198
- case never:
199
- assert_never(never)
200
- assert settings.value == expected
201
-
202
-
203
- class TestLoadSettings:
204
- @given(root=temp_paths(), date_time=zoned_date_times(), app_name=app_names)
205
- def test_main(self, *, root: Path, date_time: ZonedDateTime, app_name: str) -> None:
206
- @dataclass(frozen=True, kw_only=True, slots=True)
207
- class Settings:
208
- date_time: ZonedDateTime
209
-
210
- file = root.joinpath("settings.toml")
211
- _ = file.write_text(
212
- strip_and_dedent(f"""
213
- [{app_name}]
214
- date_time = {str(date_time)!r}
215
- """)
216
- )
217
- settings = load_settings(Settings, app_name, start_dir=root)
218
- assert settings.date_time == date_time
219
-
220
- @given(
221
- prefix=app_names.map(lambda text: f"TEST_{text}".upper()),
222
- date_time=zoned_date_times(),
223
- app_name=app_names,
224
- )
225
- def test_loaders(
226
- self, *, prefix: str, date_time: ZonedDateTime, app_name: str
227
- ) -> None:
228
- key = f"{prefix}__DATE_TIME"
229
-
230
- @dataclass(frozen=True, kw_only=True, slots=True)
231
- class Settings:
232
- date_time: ZonedDateTime
233
-
234
- with temp_environ({key: str(date_time)}):
235
- settings = load_settings(
236
- Settings, app_name, loaders=[EnvLoader(prefix=f"{prefix}__")]
237
- )
238
- assert settings.date_time == date_time
239
-
240
- @given(root=temp_paths(), app_name=app_names)
241
- def test_converter_simple(self, *, root: Path, app_name: str) -> None:
242
- @dataclass(frozen=True, kw_only=True, slots=True)
243
- class Settings:
244
- sentinel: Sentinel
245
-
246
- file = root.joinpath("settings.toml")
247
- _ = file.write_text(
248
- strip_and_dedent(f"""
249
- [{app_name}]
250
- sentinel = 'sentinel'
251
- """)
252
- )
253
-
254
- def convert(text: str, /) -> Sentinel:
255
- if text == "sentinel":
256
- return sentinel
257
- raise ValueError
258
-
259
- settings = load_settings(
260
- Settings, app_name, start_dir=root, converters=[(Sentinel, convert)]
261
- )
262
- assert settings.sentinel is sentinel
263
-
264
- @given(data=data(), root=temp_paths(), n=integers(), app_name=app_names)
265
- def test_converter_dataclass(
266
- self, *, data: DataObject, root: Path, n: int, app_name: str
267
- ) -> None:
268
- @dataclass(repr=False, frozen=True, kw_only=True, slots=True)
269
- class Left:
270
- x: int
271
-
272
- @override
273
- def __str__(self) -> str:
274
- return f"left{self.x}"
275
-
276
- @classmethod
277
- def parse(cls, text: str, /) -> Self:
278
- x = extract_group(r"^left(.+?)$", text)
279
- return cls(x=int(x))
280
-
281
- @dataclass(frozen=True, kw_only=True, slots=True)
282
- class Right:
283
- y: int
284
-
285
- @override
286
- def __str__(self) -> str:
287
- return f"right{self.y}"
288
-
289
- @classmethod
290
- def parse(cls, text: str, /) -> Self:
291
- y = extract_group(r"^right(.+?)$", text)
292
- return cls(y=int(y))
293
-
294
- value = data.draw(sampled_from([Left(x=n), Right(y=n)]))
295
-
296
- @dataclass(frozen=True, kw_only=True, slots=True)
297
- class Settings:
298
- inner: Left | Right
299
-
300
- file = root.joinpath("settings.toml")
301
- _ = file.write_text(
302
- strip_and_dedent(f"""
303
- [{app_name}]
304
- inner = {str(value)!r}
305
- """)
306
- )
307
- settings = load_settings(
308
- Settings,
309
- app_name,
310
- start_dir=root,
311
- converters=[(Left, Left.parse), (Right, Right.parse)],
312
- )
313
- assert settings.inner == value
314
-
315
- @mark.parametrize("app_name", [param("app_"), param("app1"), param("app__name")])
316
- def test_error(self, *, app_name: str) -> None:
317
- @dataclass(frozen=True, kw_only=True, slots=True)
318
- class Settings: ...
319
-
320
- with raises(LoadSettingsError, match=r"Invalid app name; got '.+'"):
321
- _ = load_settings(Settings, app_name)