dycw-utilities 0.132.1__tar.gz → 0.132.3__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.
Files changed (220) hide show
  1. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/PKG-INFO +1 -2
  2. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/pyproject.toml +81 -71
  3. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/conftest.py +2 -1
  4. dycw_utilities-0.132.3/src/tests/test_typed_settings.py +147 -0
  5. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/__init__.py +1 -1
  6. dycw_utilities-0.132.3/src/utilities/typed_settings.py +124 -0
  7. dycw_utilities-0.132.1/src/tests/test_pyrsistent.py +0 -49
  8. dycw_utilities-0.132.1/src/tests/test_typed_settings.py +0 -99
  9. dycw_utilities-0.132.1/src/utilities/pyrsistent.py +0 -89
  10. dycw_utilities-0.132.1/src/utilities/typed_settings.py +0 -61
  11. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/.gitignore +0 -0
  12. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/LICENSE +0 -0
  13. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/README.md +0 -0
  14. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/__init__.py +0 -0
  15. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/modules/__init__.py +0 -0
  16. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/modules/package_missing/__init__.py +0 -0
  17. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/modules/package_missing/module.py +0 -0
  18. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/modules/package_with/__init__.py +0 -0
  19. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/modules/package_with/outer_1.py +0 -0
  20. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/modules/package_with/outer_2.py +0 -0
  21. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/modules/package_with/subpackage/__init__.py +0 -0
  22. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/modules/package_with/subpackage/inner_1.py +0 -0
  23. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/modules/package_with/subpackage/inner_2.py +0 -0
  24. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/modules/package_with/subpackage/inner_3.py +0 -0
  25. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/modules/package_without/__init__.py +0 -0
  26. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/modules/package_without/module_1.py +0 -0
  27. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/modules/package_without/module_2.py +0 -0
  28. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/modules/standalone.py +0 -0
  29. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/modules/with_imports.py +0 -0
  30. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -0
  31. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -0
  32. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -0
  33. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -0
  34. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -0
  35. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -0
  36. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -0
  37. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -0
  38. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_aiolimiter.py +0 -0
  39. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_altair.py +0 -0
  40. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_asyncio.py +0 -0
  41. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_asyncio_classes/__init__.py +0 -0
  42. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_asyncio_classes/loopers.py +0 -0
  43. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_asyncio_classes/redis.py +0 -0
  44. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_atomicwrites.py +0 -0
  45. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_atools.py +0 -0
  46. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_cachetools.py +0 -0
  47. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_click.py +0 -0
  48. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_concurrent.py +0 -0
  49. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_contextlib.py +0 -0
  50. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_contextvars.py +0 -0
  51. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_cryptography.py +0 -0
  52. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_cvxpy.py +0 -0
  53. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_dataclasses.py +0 -0
  54. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_enum.py +0 -0
  55. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_errors.py +0 -0
  56. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_eventkit.py +0 -0
  57. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_fastapi.py +0 -0
  58. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_fpdf2.py +0 -0
  59. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_functions.py +0 -0
  60. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_functools.py +0 -0
  61. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_getpass.py +0 -0
  62. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_git.py +0 -0
  63. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_hashlib.py +0 -0
  64. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_http.py +0 -0
  65. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_hypothesis.py +0 -0
  66. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_importlib.py +0 -0
  67. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_inflect.py +0 -0
  68. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_ipython.py +0 -0
  69. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_iterables.py +0 -0
  70. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_jupyter.py +0 -0
  71. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_libcst.py +0 -0
  72. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_lightweight_charts.py +0 -0
  73. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_logging.py +0 -0
  74. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_luigi.py +0 -0
  75. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_math.py +0 -0
  76. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_memory_profiler.py +0 -0
  77. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_modules.py +0 -0
  78. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_more_itertools.py +0 -0
  79. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_numpy.py +0 -0
  80. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_objects/__init__.py +0 -0
  81. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_objects/objects.py +0 -0
  82. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_operator.py +0 -0
  83. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_optuna.py +0 -0
  84. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_orjson.py +0 -0
  85. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_os.py +0 -0
  86. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_parse.py +0 -0
  87. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_pathlib.py +0 -0
  88. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_period.py +0 -0
  89. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_pickle.py +0 -0
  90. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_platform.py +0 -0
  91. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_polars.py +0 -0
  92. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_polars_ols.py +0 -0
  93. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_pottery.py +0 -0
  94. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_pqdm.py +0 -0
  95. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_psutil.py +0 -0
  96. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_pydantic.py +0 -0
  97. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_pyinstrument.py +0 -0
  98. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_pytest.py +0 -0
  99. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_pytest_regressions.py +0 -0
  100. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_python_dotenv.py +0 -0
  101. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_random.py +0 -0
  102. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_re.py +0 -0
  103. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_redis.py +0 -0
  104. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_reprlib.py +0 -0
  105. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_scipy.py +0 -0
  106. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_sentinel.py +0 -0
  107. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_shelve.py +0 -0
  108. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_slack_sdk.py +0 -0
  109. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_socket.py +0 -0
  110. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_sqlalchemy.py +0 -0
  111. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_sqlalchemy_polars.py +0 -0
  112. /dycw_utilities-0.132.1/src/tests/test_statsmodel.py → /dycw_utilities-0.132.3/src/tests/test_statsmodels.py +0 -0
  113. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_streamlit.py +0 -0
  114. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_string.py +0 -0
  115. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_tempfile.py +0 -0
  116. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_text.py +0 -0
  117. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_threading.py +0 -0
  118. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_timer.py +0 -0
  119. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_traceback.py +0 -0
  120. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_types.py +0 -0
  121. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_typing.py +0 -0
  122. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_typing_funcs/__init__.py +0 -0
  123. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_typing_funcs/no_future.py +0 -0
  124. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_typing_funcs/with_future.py +0 -0
  125. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_tzdata.py +0 -0
  126. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_tzlocal.py +0 -0
  127. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_uuid.py +0 -0
  128. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_version.py +0 -0
  129. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_warnings.py +0 -0
  130. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_whenever.py +0 -0
  131. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_zipfile.py +0 -0
  132. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_zoneinfo.py +0 -0
  133. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/aiolimiter.py +0 -0
  134. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/altair.py +0 -0
  135. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/asyncio.py +0 -0
  136. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/atomicwrites.py +0 -0
  137. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/atools.py +0 -0
  138. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/cachetools.py +0 -0
  139. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/click.py +0 -0
  140. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/concurrent.py +0 -0
  141. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/contextlib.py +0 -0
  142. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/contextvars.py +0 -0
  143. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/cryptography.py +0 -0
  144. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/cvxpy.py +0 -0
  145. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/dataclasses.py +0 -0
  146. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/enum.py +0 -0
  147. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/errors.py +0 -0
  148. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/eventkit.py +0 -0
  149. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/fastapi.py +0 -0
  150. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/fpdf2.py +0 -0
  151. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/functions.py +0 -0
  152. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/functools.py +0 -0
  153. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/getpass.py +0 -0
  154. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/git.py +0 -0
  155. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/hashlib.py +0 -0
  156. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/http.py +0 -0
  157. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/hypothesis.py +0 -0
  158. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/importlib.py +0 -0
  159. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/inflect.py +0 -0
  160. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/ipython.py +0 -0
  161. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/iterables.py +0 -0
  162. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/jupyter.py +0 -0
  163. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/libcst.py +0 -0
  164. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/lightweight_charts.py +0 -0
  165. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/logging.py +0 -0
  166. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/luigi.py +0 -0
  167. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/math.py +0 -0
  168. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/memory_profiler.py +0 -0
  169. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/modules.py +0 -0
  170. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/more_itertools.py +0 -0
  171. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/numpy.py +0 -0
  172. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/operator.py +0 -0
  173. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/optuna.py +0 -0
  174. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/orjson.py +0 -0
  175. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/os.py +0 -0
  176. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/parse.py +0 -0
  177. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/pathlib.py +0 -0
  178. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/period.py +0 -0
  179. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/pickle.py +0 -0
  180. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/platform.py +0 -0
  181. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/polars.py +0 -0
  182. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/polars_ols.py +0 -0
  183. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/pottery.py +0 -0
  184. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/pqdm.py +0 -0
  185. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/psutil.py +0 -0
  186. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/py.typed +0 -0
  187. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/pydantic.py +0 -0
  188. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/pyinstrument.py +0 -0
  189. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/pytest.py +0 -0
  190. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/pytest_regressions.py +0 -0
  191. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/python_dotenv.py +0 -0
  192. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/random.py +0 -0
  193. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/re.py +0 -0
  194. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/redis.py +0 -0
  195. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/reprlib.py +0 -0
  196. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/scipy.py +0 -0
  197. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/sentinel.py +0 -0
  198. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/shelve.py +0 -0
  199. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/slack_sdk.py +0 -0
  200. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/socket.py +0 -0
  201. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/sqlalchemy.py +0 -0
  202. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/sqlalchemy_polars.py +0 -0
  203. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/statsmodels.py +0 -0
  204. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/streamlit.py +0 -0
  205. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/string.py +0 -0
  206. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/tempfile.py +0 -0
  207. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/text.py +0 -0
  208. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/threading.py +0 -0
  209. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/timer.py +0 -0
  210. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/traceback.py +0 -0
  211. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/types.py +0 -0
  212. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/typing.py +0 -0
  213. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/tzdata.py +0 -0
  214. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/tzlocal.py +0 -0
  215. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/uuid.py +0 -0
  216. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/version.py +0 -0
  217. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/warnings.py +0 -0
  218. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/whenever.py +0 -0
  219. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/zipfile.py +0 -0
  220. {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/zoneinfo.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.132.1
3
+ Version: 0.132.3
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -17,7 +17,6 @@ Requires-Dist: pytest-asyncio<1.1,>=1.0.0; extra == 'test'
17
17
  Requires-Dist: pytest-cov<6.2,>=6.1.1; extra == 'test'
18
18
  Requires-Dist: pytest-instafail<0.6,>=0.5.0; extra == 'test'
19
19
  Requires-Dist: pytest-lazy-fixtures<1.2,>=1.1.4; extra == 'test'
20
- Requires-Dist: pytest-randomly<3.17,>=3.16.0; extra == 'test'
21
20
  Requires-Dist: pytest-regressions<2.9,>=2.8.0; extra == 'test'
22
21
  Requires-Dist: pytest-rerunfailures<16,>=15.1; extra == 'test'
23
22
  Requires-Dist: pytest-rng<1.1,>=1.0.0; extra == 'test'
@@ -7,80 +7,88 @@ requires = ["hatchling"]
7
7
 
8
8
  # dependency groups
9
9
  [dependency-groups]
10
+ aiolimiter = ["aiolimiter >= 1.2.1, < 1.3"]
11
+ altair = ["altair >= 5.5.0, < 5.6"]
12
+ altair-test = [
13
+ "polars-lts-cpu",
14
+ "img2pdf",
15
+ "vl-convert-python",
16
+ ]
17
+ atools = ["atools >= 0.14.2, < 0.15"]
18
+ cachetools = ["cachetools >= 5.5.2, < 5.6"]
19
+ click = ["click >= 8.2.1, < 8.3"]
20
+ core = [
21
+ "atomicwrites >= 1.4.1, < 1.5",
22
+ "typing-extensions >= 4.14.0, < 4.15",
23
+ "tzlocal >= 5.3.1, < 5.4",
24
+ "whenever >= 0.8.5, < 0.9",
25
+ ]
26
+ cryptography = ["cryptography >= 45.0.4, < 45.1"]
27
+ cvxpy = ["cvxpy >= 1.6.5, < 1.7"]
28
+ dataclasses-test = ["orjson", "polars-lts-cpu"]
10
29
  dev = [
11
- "aiohttp >= 3.12.12, < 3.13", # for slack
12
- "aiolimiter >= 1.2.1, < 1.3",
13
- "aiosqlite >= 0.21.0, < 0.22",
14
- "altair >= 5.5.0, < 5.6",
15
- "asyncpg >= 0.30.0, < 0.31", # for sqlalchemy async
16
- "atools >= 0.14.2, < 0.15",
17
- "cachetools >= 5.5.2, < 5.6", # blocked by streamlit
18
- "click >= 8.2.1, < 8.3",
19
30
  "coloredlogs >= 15.0.1, < 15.1",
20
- "cryptography >= 45.0.4, < 45.1",
21
- "cvxpy >= 1.6.5, < 1.7",
22
- "eventkit >= 1.0.3, < 1.1",
23
- "fastapi >= 0.115.11, < 0.116",
24
- "fpdf2 >= 2.8.3, < 2.9",
25
- "greenlet >= 3.2.0, < 3.3", # for sqlalchemy async
26
- "httpx >= 0.28.1, < 0.29", # for fastapi
27
- "hypothesis >= 6.135.2, < 6.136",
28
- "img2pdf >= 0.6.0, < 0.7",
29
- "inflect >= 7.5.0, < 7.6",
30
- "lightweight-charts >= 2.1, < 2.2",
31
- "luigi >= 3.6.0, < 3.7",
32
- "memory-profiler >= 0.61.0, < 0.62",
33
- "more-itertools >= 10.7.0, < 10.8",
34
- "nest-asyncio >= 1.6.0, < 1.7", # for sqlalchemy async
35
- "nox >= 2025.2.9, < 2025.3",
36
- "numpy >= 2.3.0, < 2.4",
37
- "libcst >= 1.8.0, < 1.9",
38
- "optuna >= 4.3.0, < 4.4",
39
- "orjson >= 3.10.18, < 3.11",
40
- "pathvalidate >= 3.2.3, < 3.3",
41
- "polars-lts-cpu >= 1.30.0, < 1.31",
42
- "polars-ols >= 0.3.5, < 0.4",
43
- "pottery >= 3.0.1, < 3.1",
44
- "pqdm >= 0.2.0, < 0.3",
45
- "psutil >= 7.0.0, < 7.1",
46
- "psycopg2-binary >= 2.9.10, < 2.10", # for sqlalchemy
47
- "pydantic >= 2.11.4, < 2.12",
48
- "pyinstrument >= 5.0.2, < 5.1",
31
+ "coverage-conditional-plugin >= 0.9.0, < 0.10",
49
32
  "pyright[nodejs] >= 1.1.401, < 1.2",
50
- "pyrsistent >= 0.20.0, < 0.21",
33
+ "pytest-cov >= 6.1.1, < 6.2",
34
+ ]
35
+ eventkit = ["eventkit >= 1.0.3, < 1.1"]
36
+ fastapi = ["fastapi >= 0.115.11, < 0.116"]
37
+ fastapi-test = ["httpx", "uvicorn"]
38
+ fpdf2 = ["fpdf2 >= 2.8.3, < 2.9"]
39
+ hashlib-test = ["orjson"]
40
+ http-test = ["orjson"]
41
+ hypothesis = ["hypothesis >= 6.135.2, < 6.136"]
42
+ hypothesis-test = ["luigi", "pathvalidate", "numpy", "pytest-rerunfailures"]
43
+ inflect = ["inflect >= 7.5.0, < 7.6"]
44
+ jupyter-test = ["pandas", "polars"]
45
+ libcst = ["libcst >= 1.8.0, < 1.9"]
46
+ lightweight-charts = ["lightweight-charts >= 2.1, < 2.2"]
47
+ lightweight-charts-test = ["polars-lts-cpu", "pyarrow"]
48
+ luigi = ["luigi >= 3.6.0, < 3.7"]
49
+ math-test = ["numpy"]
50
+ memory-profiler = ["memory-profiler >= 0.61.0, < 0.62"]
51
+ more-itertools = ["more-itertools >= 10.7.0, < 10.8"]
52
+ numpy = ["numpy >= 2.3.0, < 2.4"]
53
+ operator = ["polars-lts-cpu"]
54
+ optuna = ["optuna >= 4.3.0, < 4.4"]
55
+ orjson = ["orjson >= 3.10.18, < 3.11"]
56
+ orjson-test = ["polars-lts-cpu"]
57
+ polars = ["polars-lts-cpu >= 1.30.0, < 1.31"]
58
+ polars-ols = ["polars-ols >= 0.3.5, < 0.4"]
59
+ polars-ols-test = ["scikit-learn"]
60
+ polars-test = ["numpy", "statsmodels"]
61
+ pottery = ["pottery >= 3.0.1, < 3.1"]
62
+ pottery-test = ["orjson", "pytest-rerunfailures"]
63
+ pqdm = ["pqdm >= 0.2.0, < 0.3"]
64
+ psutil = ["psutil >= 7.0.0, < 7.1"]
65
+ pydantic = ["pydantic >= 2.11.4, < 2.12"]
66
+ pyinstrument = ["pyinstrument >= 5.0.2, < 5.1"]
67
+ pytest = [
51
68
  "pytest >= 8.3.5, < 8.4",
52
- "pytest-regressions >= 2.8.0, < 2.9",
53
- "python-dotenv >= 1.1.0, < 1.2",
54
- "redis >= 6.2.0, < 6.3",
55
- "rich >= 14.0.0, < 14.1",
56
- "scikit-learn >= 1.7.0, < 1.8",
57
- "scipy >= 1.15.3, < 1.16",
58
- "slack-sdk >= 3.35.0, < 3.36",
59
- "sqlalchemy >= 2.0.41, < 2.1",
60
- "statsmodels >= 0.14.4, < 0.15",
61
- "streamlit >= 1.45.0, < 1.46",
62
- "tomlkit >= 0.13.2, < 0.14",
63
- "typed-settings >= 24.6.0, < 24.7",
64
- "tzdata >= 2025.2, < 2025.3",
65
- "uvicorn >= 0.34.1, < 0.35",
66
- "vegafusion >= 2.0.2, < 2.1",
67
- "vegafusion-python-embed >= 1.6.9, < 1.7",
68
- "vl-convert-python >= 1.8.0, < 1.9",
69
- # test
70
- "coverage-conditional-plugin >= 0.9.0, < 0.10",
71
- "dycw-pytest-only >= 2.1.1, < 2.2",
72
69
  "pytest-asyncio >= 1.0.0, < 1.1",
73
- "pytest-cov >= 6.1.1, < 6.2",
74
- "pytest-instafail >= 0.5.0, < 0.6",
75
- "pytest-lazy-fixtures >= 1.1.4, < 1.2",
76
70
  "pytest-randomly >= 3.16.0, < 3.17",
77
- "pytest-rerunfailures >= 15.1, < 16",
78
- "pytest-rng >= 1.0.0, < 1.1",
79
- "pytest-timeout >= 2.4.0, < 2.5",
80
71
  "pytest-xdist >= 3.7.0, < 3.8",
81
- # CI
82
- "aiolimiter>=1.2.1",
83
72
  ]
73
+ pytest-regressions = ["pytest-regressions >= 2.8.0, < 2.9"]
74
+ pytest-regressions-test = ["orjson", "polars-lts-cpu"]
75
+ pytest-test = ["orjson", "pytest-rng", "pytest-rerunfailures"]
76
+ python-dotenv = ["python-dotenv >= 1.1.0, < 1.2"]
77
+ redis = ["redis >= 6.2.0, < 6.3", "orjson"]
78
+ redis-test = ["pytest-rerunfailures"]
79
+ reprlib-test = ["rich"]
80
+ scipy = ["scipy >= 1.15.3, < 1.16"]
81
+ sklearn = ["scikit-learn >= 1.7.0, < 1.8"]
82
+ slack-sdk = ["slack-sdk >= 3.35.0, < 3.36"]
83
+ slack-sdk-test = ["aiohttp"]
84
+ sqlalchemy = ["sqlalchemy >= 2.0.41, < 2.1"]
85
+ sqlalchemy-polars = ["sqlalchemy", "polars-lts-cpu"]
86
+ sqlalchemy-polars-test = ["aiosqlite", "asyncpg", "greenlet"]
87
+ sqlalchemy-test = ["aiosqlite", "asyncpg", "greenlet"]
88
+ statsmodels = ["statsmodels >= 0.14.4, < 0.15"]
89
+ streamlit = ["streamlit >= 1.45.0, < 1.46"]
90
+ typed-settings = ["typed-settings >= 24.6.0, < 24.7"]
91
+ tzdata = ["tzdata >= 2025.2, < 2025.3"]
84
92
 
85
93
  # project
86
94
  [project]
@@ -94,7 +102,7 @@ dependencies = [
94
102
  name = "dycw-utilities"
95
103
  readme = "README.md"
96
104
  requires-python = ">= 3.12"
97
- version = "0.132.1"
105
+ version = "0.132.3"
98
106
 
99
107
  [project.optional-dependencies]
100
108
  logging = [
@@ -108,7 +116,6 @@ test = [
108
116
  "pytest-cov >= 6.1.1, < 6.2",
109
117
  "pytest-instafail >= 0.5.0, < 0.6",
110
118
  "pytest-lazy-fixtures >= 1.1.4, < 1.2",
111
- "pytest-randomly >= 3.16.0, < 3.17",
112
119
  "pytest-regressions >= 2.8.0, < 2.9",
113
120
  "pytest-rerunfailures >= 15.1, < 16",
114
121
  "pytest-rng >= 1.0.0, < 1.1",
@@ -121,7 +128,7 @@ test = [
121
128
  # bump-my-version
122
129
  [tool.bumpversion]
123
130
  allow_dirty = true
124
- current_version = "0.132.1"
131
+ current_version = "0.132.3"
125
132
 
126
133
  [[tool.bumpversion.files]]
127
134
  filename = "src/utilities/__init__.py"
@@ -223,7 +230,6 @@ addopts = [
223
230
  "--durations=10",
224
231
  "--durations-min=10",
225
232
  "--strict-markers",
226
- "--timeout=300",
227
233
  ]
228
234
  asyncio_default_fixture_loop_scope = "function"
229
235
  asyncio_mode = "auto"
@@ -233,7 +239,7 @@ filterwarnings = [
233
239
  "ignore:Implicitly cleaning up <TemporaryDirectory '.*'>:ResourceWarning",
234
240
  "ignore:ResourceTracker called reentrantly for resource cleanup, which is unsupported:UserWarning",
235
241
  "ignore:Task .* without outputs has no custom complete.* method:UserWarning", # luigi
236
- "ignore:The garbage collector is trying to clean up non-checked-in connection <AdaptedConnection <Connection(.*)>>:sqlalchemy.exc.SAWarning", # sqlalchemy
242
+ "ignore:The garbage collector is trying to clean up non-checked-in connection <AdaptedConnection <Connection(.*)>:RuntimeWarning", # sqlalchemy
237
243
  "ignore:There is no current event loop:DeprecationWarning", # eventkit
238
244
  "ignore:Using fork.* can cause Polars to deadlock in the child process:RuntimeWarning", # polars/pqdm
239
245
  "ignore:coroutine 'AsyncConnection.close' was never awaited:RuntimeWarning",
@@ -328,3 +334,7 @@ ban-relative-imports = "all"
328
334
  [tool.ruff.lint.isort]
329
335
  required-imports = ["from __future__ import annotations"]
330
336
  split-on-trailing-comma = false
337
+
338
+ # uv
339
+ [tool.uv]
340
+ default-groups = "all"
@@ -8,7 +8,6 @@ from typing import TYPE_CHECKING, Any
8
8
 
9
9
  from hypothesis import HealthCheck
10
10
  from pytest import fixture, mark, param
11
- from sqlalchemy import text
12
11
  from whenever import PlainDateTime
13
12
 
14
13
  from utilities.platform import IS_NOT_LINUX, IS_WINDOWS
@@ -64,6 +63,8 @@ def set_log_factory() -> AbstractContextManager[None]:
64
63
 
65
64
  @fixture(params=[param("sqlite"), param("postgresql", marks=SKIPIF_CI)])
66
65
  async def test_engine(*, request: SubRequest, tmp_path: Path) -> Any:
66
+ from sqlalchemy import text
67
+
67
68
  from utilities.sqlalchemy import create_async_engine
68
69
 
69
70
  dialect = request.param
@@ -0,0 +1,147 @@
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 TypeVar
6
+
7
+ import typed_settings
8
+ from hypothesis import given
9
+ from hypothesis.strategies import DataObject, SearchStrategy, data, ip_addresses, tuples
10
+ from pytest import mark, param, raises
11
+ from typed_settings import EnvLoader, FileLoader, TomlFormat
12
+ from whenever import (
13
+ Date,
14
+ DateDelta,
15
+ DateTimeDelta,
16
+ PlainDateTime,
17
+ Time,
18
+ TimeDelta,
19
+ ZonedDateTime,
20
+ )
21
+
22
+ from utilities.hypothesis import (
23
+ date_deltas,
24
+ date_time_deltas,
25
+ dates,
26
+ plain_datetimes,
27
+ temp_paths,
28
+ text_ascii,
29
+ time_deltas,
30
+ times,
31
+ zoned_datetimes,
32
+ )
33
+ from utilities.os import temp_environ
34
+ from utilities.text import strip_and_dedent
35
+ from utilities.typed_settings import (
36
+ ExtendedTSConverter,
37
+ LoadSettingsError,
38
+ load_settings,
39
+ )
40
+
41
+ app_names = text_ascii(min_size=1).map(str.lower)
42
+
43
+
44
+ _T = TypeVar("_T")
45
+
46
+
47
+ class TestExtendedTSConverter:
48
+ @given(data=data(), root=temp_paths(), app_name=app_names)
49
+ @mark.parametrize(
50
+ ("test_cls", "strategy", "serialize"),
51
+ [
52
+ param(Date, dates(), Date.format_common_iso),
53
+ param(DateDelta, date_deltas(parsable=True), DateDelta.format_common_iso),
54
+ param(
55
+ DateTimeDelta,
56
+ date_time_deltas(parsable=True),
57
+ DateTimeDelta.format_common_iso,
58
+ ),
59
+ param(IPv4Address, ip_addresses(v=4), IPv4Address),
60
+ param(IPv6Address, ip_addresses(v=6), IPv6Address),
61
+ param(PlainDateTime, plain_datetimes(), PlainDateTime.format_common_iso),
62
+ param(Time, times(), Time.format_common_iso),
63
+ param(TimeDelta, time_deltas(), TimeDelta.format_common_iso),
64
+ param(ZonedDateTime, zoned_datetimes(), ZonedDateTime.format_common_iso),
65
+ ],
66
+ )
67
+ def test_main(
68
+ self,
69
+ *,
70
+ data: DataObject,
71
+ root: Path,
72
+ app_name: str,
73
+ test_cls: type[_T],
74
+ strategy: SearchStrategy[_T],
75
+ serialize: Callable[[_T], str],
76
+ ) -> None:
77
+ default, value = data.draw(tuples(strategy, strategy))
78
+
79
+ @dataclass(frozen=True, kw_only=True, slots=True)
80
+ class Settings:
81
+ value: test_cls = default # pyright: ignore[reportInvalidTypeForm]
82
+
83
+ settings_default = typed_settings.load_settings(
84
+ Settings, loaders=[], converter=ExtendedTSConverter()
85
+ )
86
+ assert settings_default.value == default
87
+ file = Path(root, "file.toml")
88
+ _ = file.write_text(
89
+ strip_and_dedent(f"""
90
+ [{app_name}]
91
+ value = '{serialize(value)}'
92
+ """)
93
+ )
94
+ settings_loaded = typed_settings.load_settings(
95
+ Settings,
96
+ loaders=[
97
+ FileLoader(formats={"*.toml": TomlFormat(app_name)}, files=[file])
98
+ ],
99
+ converter=ExtendedTSConverter(),
100
+ )
101
+ assert settings_loaded.value == value
102
+
103
+
104
+ class TestLoadSettings:
105
+ @given(root=temp_paths(), datetime=zoned_datetimes())
106
+ def test_main(self, *, root: Path, datetime: ZonedDateTime) -> None:
107
+ @dataclass(frozen=True, kw_only=True, slots=True)
108
+ class Settings:
109
+ datetime: ZonedDateTime
110
+
111
+ file = Path(root, "file.toml")
112
+ _ = file.write_text("")
113
+ _ = file.write_text(
114
+ strip_and_dedent(f"""
115
+ [app_name]
116
+ datetime = '{datetime.format_common_iso()}'
117
+ """)
118
+ )
119
+ settings = load_settings(
120
+ Settings, "app_name", filenames="file.toml", start_dir=root
121
+ )
122
+ assert settings.datetime == datetime
123
+
124
+ @given(
125
+ prefix=app_names.map(lambda text: f"TEST_{text}".upper()),
126
+ datetime=zoned_datetimes(),
127
+ )
128
+ def test_loaders(self, *, prefix: str, datetime: ZonedDateTime) -> None:
129
+ key = f"{prefix}__DATETIME"
130
+
131
+ @dataclass(frozen=True, kw_only=True, slots=True)
132
+ class Settings:
133
+ datetime: ZonedDateTime
134
+
135
+ with temp_environ({key: datetime.format_common_iso()}):
136
+ settings = load_settings(
137
+ Settings, "app_name", loaders=[EnvLoader(prefix=f"{prefix}__")]
138
+ )
139
+ assert settings.datetime == datetime
140
+
141
+ @mark.parametrize("app_name", [param("app_"), param("app1"), param("app__name")])
142
+ def test_error(self, *, app_name: str) -> None:
143
+ @dataclass(frozen=True, kw_only=True, slots=True)
144
+ class Settings: ...
145
+
146
+ with raises(LoadSettingsError, match="Invalid app name; got '.+'"):
147
+ _ = load_settings(Settings, app_name)
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.132.1"
3
+ __version__ = "0.132.3"
@@ -0,0 +1,124 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from ipaddress import IPv4Address, IPv6Address
5
+ from pathlib import Path
6
+ from re import search
7
+ from typing import TYPE_CHECKING, Any, TypeVar, override
8
+
9
+ import typed_settings
10
+ from typed_settings import EnvLoader, FileLoader, find
11
+ from typed_settings.converters import TSConverter
12
+ from typed_settings.loaders import TomlFormat
13
+ from whenever import (
14
+ Date,
15
+ DateDelta,
16
+ DateTimeDelta,
17
+ PlainDateTime,
18
+ Time,
19
+ TimeDelta,
20
+ ZonedDateTime,
21
+ )
22
+
23
+ from utilities.iterables import always_iterable
24
+
25
+ if TYPE_CHECKING:
26
+ from collections.abc import Callable
27
+
28
+ from typed_settings.loaders import Loader
29
+ from typed_settings.processors import Processor
30
+
31
+ from utilities.types import MaybeIterable, PathLike
32
+
33
+
34
+ _T = TypeVar("_T")
35
+
36
+
37
+ ##
38
+
39
+
40
+ class ExtendedTSConverter(TSConverter):
41
+ """An extension of the TSConverter for custom types."""
42
+
43
+ @override
44
+ def __init__(
45
+ self,
46
+ *,
47
+ resolve_paths: bool = True,
48
+ strlist_sep: str | Callable[[str], list] | None = ":",
49
+ ) -> None:
50
+ super().__init__(resolve_paths=resolve_paths, strlist_sep=strlist_sep)
51
+ cases: list[tuple[type[Any], Callable[..., Any]]] = [
52
+ (Date, Date.parse_common_iso),
53
+ (DateDelta, DateDelta.parse_common_iso),
54
+ (DateTimeDelta, DateTimeDelta.parse_common_iso),
55
+ (IPv4Address, IPv4Address),
56
+ (IPv6Address, IPv6Address),
57
+ (PlainDateTime, PlainDateTime.parse_common_iso),
58
+ (Time, Time.parse_common_iso),
59
+ (TimeDelta, TimeDelta.parse_common_iso),
60
+ (ZonedDateTime, ZonedDateTime.parse_common_iso),
61
+ ]
62
+ extras = {cls: _make_converter(cls, func) for cls, func in cases}
63
+ self.scalar_converters |= extras
64
+
65
+
66
+ def _make_converter(
67
+ cls: type[_T], parser: Callable[[str], _T], /
68
+ ) -> Callable[[Any, type[Any]], Any]:
69
+ def hook(value: _T | str, _: type[_T] = cls, /) -> Any:
70
+ if not isinstance(value, (cls, str)): # pragma: no cover
71
+ msg = f"Invalid type {type(value).__name__!r}; expected '{cls.__name__}' or 'str'"
72
+ raise TypeError(msg)
73
+ if isinstance(value, str):
74
+ return parser(value)
75
+ return value
76
+
77
+ return hook
78
+
79
+
80
+ ##
81
+
82
+ _BASE_DIR: Path = Path()
83
+
84
+
85
+ def load_settings(
86
+ cls: type[_T],
87
+ app_name: str,
88
+ /,
89
+ *,
90
+ filenames: MaybeIterable[str] = "settings.toml",
91
+ start_dir: PathLike | None = None,
92
+ loaders: MaybeIterable[Loader] | None = None,
93
+ processors: MaybeIterable[Processor] = (),
94
+ base_dir: Path = _BASE_DIR,
95
+ ) -> _T:
96
+ if not search(r"^[A-Za-z]+(?:_[A-Za-z]+)*$", app_name):
97
+ raise LoadSettingsError(appname=app_name)
98
+ filenames_use = list(always_iterable(filenames))
99
+ start_dir_use = None if start_dir is None else Path(start_dir)
100
+ files = [find(filename, start_dir=start_dir_use) for filename in filenames_use]
101
+ file_loader = FileLoader(formats={"*.toml": TomlFormat(app_name)}, files=files)
102
+ env_loader = EnvLoader(f"{app_name.upper()}__", nested_delimiter="__")
103
+ loaders_use: list[Loader] = [file_loader, env_loader]
104
+ if loaders is not None:
105
+ loaders_use.extend(always_iterable(loaders))
106
+ return typed_settings.load_settings(
107
+ cls,
108
+ loaders_use,
109
+ processors=list(always_iterable(processors)),
110
+ converter=ExtendedTSConverter(),
111
+ base_dir=base_dir,
112
+ )
113
+
114
+
115
+ @dataclass(kw_only=True, slots=True)
116
+ class LoadSettingsError(Exception):
117
+ appname: str
118
+
119
+ @override
120
+ def __str__(self) -> str:
121
+ return f"Invalid app name; got {self.appname!r}"
122
+
123
+
124
+ __all__ = ["ExtendedTSConverter", "LoadSettingsError", "load_settings"]
@@ -1,49 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from types import NoneType
4
-
5
- from pyrsistent import PTypeError
6
- from pytest import raises
7
-
8
- from utilities.pyrsistent import PRecord, field
9
-
10
-
11
- class TestPRecord:
12
- def test_mandatory_field_without_type_checking(self) -> None:
13
- class ARecord(PRecord):
14
- x: int = field()
15
-
16
- r = ARecord(x=3)
17
- assert repr(r) == "ARecord(x=3)"
18
- assert r.x == 3
19
- assert r.set(x=2) == ARecord(x=2)
20
- with raises(
21
- AttributeError, match="'y' is not among the specified fields for ARecord"
22
- ):
23
- _ = r.set(y=2)
24
-
25
- def test_optional_field_without_type_checking(self) -> None:
26
- class ARecord(PRecord):
27
- x: int | None = field(default=None)
28
-
29
- r = ARecord()
30
- assert repr(r) == "ARecord(x=None)"
31
- assert r.x is None
32
-
33
- def test_mandatory_field_with_type_checking(self) -> None:
34
- class ARecord(PRecord):
35
- x: int = field(type=int)
36
-
37
- r = ARecord(x=3)
38
- assert repr(r) == "ARecord(x=3)"
39
- with raises(PTypeError, match="Invalid type for field ARecord.x, was str"):
40
- _ = r.set(x="2")
41
-
42
- def test_optional_field_with_type_checking(self) -> None:
43
- class ARecord(PRecord):
44
- x: int | None = field(type=(int, NoneType), default=None)
45
-
46
- r = ARecord()
47
- assert repr(r) == "ARecord(x=None)"
48
- with raises(PTypeError, match="Invalid type for field ARecord.x, was str"):
49
- _ = r.set(x="2")
@@ -1,99 +0,0 @@
1
- from collections.abc import Callable
2
- from dataclasses import dataclass
3
- from operator import eq
4
- from pathlib import Path
5
- from typing import TypeVar
6
-
7
- from hypothesis import given
8
- from hypothesis.strategies import DataObject, SearchStrategy, data, tuples
9
- from pytest import mark, param
10
- from typed_settings import FileLoader, TomlFormat, load_settings
11
- from whenever import (
12
- Date,
13
- DateDelta,
14
- DateTimeDelta,
15
- PlainDateTime,
16
- Time,
17
- TimeDelta,
18
- ZonedDateTime,
19
- )
20
-
21
- from utilities.hypothesis import (
22
- date_deltas,
23
- date_time_deltas,
24
- dates,
25
- plain_datetimes,
26
- temp_paths,
27
- text_ascii,
28
- time_deltas,
29
- times,
30
- zoned_datetimes,
31
- )
32
- from utilities.typed_settings import ExtendedTSConverter
33
-
34
- app_names = text_ascii(min_size=1).map(str.lower)
35
-
36
-
37
- _T = TypeVar("_T")
38
-
39
-
40
- class TestExtendedTSConverter:
41
- @given(data=data(), root=temp_paths(), appname=text_ascii(min_size=1))
42
- @mark.parametrize(
43
- ("test_cls", "strategy", "serialize"),
44
- [
45
- param(Date, dates(), Date.format_common_iso),
46
- param(DateDelta, date_deltas(parsable=True), DateDelta.format_common_iso),
47
- param(
48
- DateTimeDelta,
49
- date_time_deltas(parsable=True),
50
- DateTimeDelta.format_common_iso,
51
- ),
52
- param(PlainDateTime, plain_datetimes(), PlainDateTime.format_common_iso),
53
- param(Time, times(), Time.format_common_iso),
54
- param(TimeDelta, time_deltas(), TimeDelta.format_common_iso),
55
- param(ZonedDateTime, zoned_datetimes(), ZonedDateTime.format_common_iso),
56
- ],
57
- )
58
- def test_main(
59
- self,
60
- *,
61
- data: DataObject,
62
- root: Path,
63
- appname: str,
64
- test_cls: type[_T],
65
- strategy: SearchStrategy[_T],
66
- serialize: Callable[[_T], str],
67
- ) -> None:
68
- default, value = data.draw(tuples(strategy, strategy))
69
- self._run_test(test_cls, default, root, appname, serialize, value, eq)
70
-
71
- def _run_test(
72
- self,
73
- test_cls: type[_T],
74
- default: _T,
75
- root: Path,
76
- appname: str,
77
- serialize: Callable[[_T], str],
78
- value: _T,
79
- equal: Callable[[_T, _T], bool],
80
- /,
81
- ) -> None:
82
- @dataclass(frozen=True, kw_only=True, slots=True)
83
- class Settings:
84
- value: test_cls = default # pyright: ignore[reportInvalidTypeForm]
85
-
86
- settings_default = load_settings(
87
- Settings, loaders=[], converter=ExtendedTSConverter()
88
- )
89
- assert settings_default.value == default
90
- _ = hash(settings_default)
91
- file = Path(root, "file.toml")
92
- with file.open(mode="w") as fh:
93
- _ = fh.write(f'[{appname}]\nvalue = "{serialize(value)}"')
94
- settings_loaded = load_settings(
95
- Settings,
96
- loaders=[FileLoader(formats={"*.toml": TomlFormat(appname)}, files=[file])],
97
- converter=ExtendedTSConverter(),
98
- )
99
- assert equal(settings_loaded.value, value)