dycw-utilities 0.108.2__tar.gz → 0.108.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.
Files changed (224) hide show
  1. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/PKG-INFO +5 -5
  2. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/pyproject.toml +8 -8
  3. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_dataclasses.py +82 -20
  4. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_functions.py +21 -0
  5. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_parse.py +8 -0
  6. dycw_utilities-0.108.4/src/tests/test_python_dotenv.py +148 -0
  7. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/__init__.py +1 -1
  8. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/dataclasses.py +166 -18
  9. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/functions.py +28 -0
  10. dycw_utilities-0.108.4/src/utilities/python_dotenv.py +116 -0
  11. dycw_utilities-0.108.2/src/tests/test_python_dotenv.py +0 -452
  12. dycw_utilities-0.108.2/src/utilities/python_dotenv.py +0 -259
  13. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/.gitignore +0 -0
  14. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/LICENSE +0 -0
  15. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/README.md +0 -0
  16. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/__init__.py +0 -0
  17. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/conftest.py +0 -0
  18. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/modules/__init__.py +0 -0
  19. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/modules/package_missing/__init__.py +0 -0
  20. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/modules/package_missing/module.py +0 -0
  21. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/modules/package_with/__init__.py +0 -0
  22. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/modules/package_with/outer_1.py +0 -0
  23. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/modules/package_with/outer_2.py +0 -0
  24. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/modules/package_with/subpackage/__init__.py +0 -0
  25. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/modules/package_with/subpackage/inner_1.py +0 -0
  26. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/modules/package_with/subpackage/inner_2.py +0 -0
  27. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/modules/package_with/subpackage/inner_3.py +0 -0
  28. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/modules/package_without/__init__.py +0 -0
  29. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/modules/package_without/module_1.py +0 -0
  30. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/modules/package_without/module_2.py +0 -0
  31. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/modules/standalone.py +0 -0
  32. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/modules/with_imports.py +0 -0
  33. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -0
  34. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -0
  35. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -0
  36. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -0
  37. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -0
  38. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -0
  39. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -0
  40. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -0
  41. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/scripts/__init__.py +0 -0
  42. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/scripts/test_async_service/__init__.py +0 -0
  43. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/scripts/test_async_service/__main__.py +0 -0
  44. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/scripts/test_async_service/run.sh +0 -0
  45. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/scripts/test_queue_processor/__init__.py +0 -0
  46. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/scripts/test_queue_processor/__main__.py +0 -0
  47. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/scripts/test_queue_processor/run.sh +0 -0
  48. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_altair.py +0 -0
  49. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_astor.py +0 -0
  50. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_asyncio.py +0 -0
  51. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_atomicwrites.py +0 -0
  52. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_atools.py +0 -0
  53. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_cachetools.py +0 -0
  54. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_click.py +0 -0
  55. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_concurrent.py +0 -0
  56. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_contextlib.py +0 -0
  57. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_contextvars.py +0 -0
  58. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_cryptography.py +0 -0
  59. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_cvxpy.py +0 -0
  60. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_datetime.py +0 -0
  61. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_enum.py +0 -0
  62. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_errors.py +0 -0
  63. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_eventkit.py +0 -0
  64. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_fastapi.py +0 -0
  65. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_fpdf2.py +0 -0
  66. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_functools.py +0 -0
  67. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_getpass.py +0 -0
  68. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_git.py +0 -0
  69. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_hashlib.py +0 -0
  70. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_http.py +0 -0
  71. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_hypothesis.py +0 -0
  72. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_ipython.py +0 -0
  73. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_iterables.py +0 -0
  74. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_jupyter.py +0 -0
  75. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_logging.py +0 -0
  76. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_loguru.py +0 -0
  77. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_luigi.py +0 -0
  78. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_math.py +0 -0
  79. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_memory_profiler.py +0 -0
  80. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_modules.py +0 -0
  81. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_more_itertools.py +0 -0
  82. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_numpy.py +0 -0
  83. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_operator.py +0 -0
  84. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_optuna.py +0 -0
  85. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_orjson.py +0 -0
  86. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_os.py +0 -0
  87. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_pathlib.py +0 -0
  88. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_period.py +0 -0
  89. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_pickle.py +0 -0
  90. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_platform.py +0 -0
  91. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_polars.py +0 -0
  92. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_pqdm.py +0 -0
  93. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_pydantic.py +0 -0
  94. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_pyinstrument.py +0 -0
  95. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_pyrsistent.py +0 -0
  96. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_pytest.py +0 -0
  97. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_pytest_regressions.py +0 -0
  98. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_random.py +0 -0
  99. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_re.py +0 -0
  100. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_redis.py +0 -0
  101. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_reprlib.py +0 -0
  102. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_rich.py +0 -0
  103. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_scipy.py +0 -0
  104. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_sentinel.py +0 -0
  105. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_shelve.py +0 -0
  106. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_slack_sdk.py +0 -0
  107. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_socket.py +0 -0
  108. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_sqlalchemy.py +0 -0
  109. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_sqlalchemy_polars.py +0 -0
  110. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_streamlit.py +0 -0
  111. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_sys.py +0 -0
  112. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_tempfile.py +0 -0
  113. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_tenacity.py +0 -0
  114. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_text.py +0 -0
  115. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_threading.py +0 -0
  116. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_timer.py +0 -0
  117. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_traceback.py +0 -0
  118. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_traceback_funcs/__init__.py +0 -0
  119. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_traceback_funcs/chain.py +0 -0
  120. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_traceback_funcs/decorated_async.py +0 -0
  121. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_traceback_funcs/decorated_sync.py +0 -0
  122. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_traceback_funcs/error_bind.py +0 -0
  123. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_traceback_funcs/many.py +0 -0
  124. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_traceback_funcs/one.py +0 -0
  125. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_traceback_funcs/recursive.py +0 -0
  126. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_traceback_funcs/task_group_one.py +0 -0
  127. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_traceback_funcs/task_group_two.py +0 -0
  128. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_traceback_funcs/two.py +0 -0
  129. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_traceback_funcs/untraced.py +0 -0
  130. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_types.py +0 -0
  131. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_typing.py +0 -0
  132. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_typing_funcs/__init__.py +0 -0
  133. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_typing_funcs/no_future.py +0 -0
  134. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_typing_funcs/with_future.py +0 -0
  135. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_tzdata.py +0 -0
  136. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_tzlocal.py +0 -0
  137. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_uuid.py +0 -0
  138. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_version.py +0 -0
  139. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_warnings.py +0 -0
  140. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_whenever.py +0 -0
  141. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_zipfile.py +0 -0
  142. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/tests/test_zoneinfo.py +0 -0
  143. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/altair.py +0 -0
  144. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/astor.py +0 -0
  145. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/asyncio.py +0 -0
  146. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/atomicwrites.py +0 -0
  147. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/atools.py +0 -0
  148. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/cachetools.py +0 -0
  149. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/click.py +0 -0
  150. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/concurrent.py +0 -0
  151. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/contextlib.py +0 -0
  152. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/contextvars.py +0 -0
  153. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/cryptography.py +0 -0
  154. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/cvxpy.py +0 -0
  155. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/datetime.py +0 -0
  156. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/enum.py +0 -0
  157. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/errors.py +0 -0
  158. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/eventkit.py +0 -0
  159. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/fastapi.py +0 -0
  160. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/fpdf2.py +0 -0
  161. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/functools.py +0 -0
  162. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/getpass.py +0 -0
  163. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/git.py +0 -0
  164. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/hashlib.py +0 -0
  165. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/http.py +0 -0
  166. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/hypothesis.py +0 -0
  167. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/ipython.py +0 -0
  168. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/iterables.py +0 -0
  169. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/jupyter.py +0 -0
  170. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/logging.py +0 -0
  171. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/loguru.py +0 -0
  172. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/luigi.py +0 -0
  173. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/math.py +0 -0
  174. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/memory_profiler.py +0 -0
  175. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/modules.py +0 -0
  176. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/more_itertools.py +0 -0
  177. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/numpy.py +0 -0
  178. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/operator.py +0 -0
  179. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/optuna.py +0 -0
  180. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/orjson.py +0 -0
  181. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/os.py +0 -0
  182. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/parse.py +0 -0
  183. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/pathlib.py +0 -0
  184. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/period.py +0 -0
  185. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/pickle.py +0 -0
  186. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/platform.py +0 -0
  187. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/polars.py +0 -0
  188. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/pqdm.py +0 -0
  189. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/py.typed +0 -0
  190. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/pydantic.py +0 -0
  191. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/pyinstrument.py +0 -0
  192. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/pyrsistent.py +0 -0
  193. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/pytest.py +0 -0
  194. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/pytest_regressions.py +0 -0
  195. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/random.py +0 -0
  196. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/re.py +0 -0
  197. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/redis.py +0 -0
  198. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/reprlib.py +0 -0
  199. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/rich.py +0 -0
  200. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/scipy.py +0 -0
  201. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/sentinel.py +0 -0
  202. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/shelve.py +0 -0
  203. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/slack_sdk.py +0 -0
  204. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/socket.py +0 -0
  205. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/sqlalchemy.py +0 -0
  206. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/sqlalchemy_polars.py +0 -0
  207. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/streamlit.py +0 -0
  208. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/sys.py +0 -0
  209. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/tempfile.py +0 -0
  210. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/tenacity.py +0 -0
  211. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/text.py +0 -0
  212. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/threading.py +0 -0
  213. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/timer.py +0 -0
  214. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/traceback.py +0 -0
  215. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/types.py +0 -0
  216. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/typing.py +0 -0
  217. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/tzdata.py +0 -0
  218. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/tzlocal.py +0 -0
  219. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/uuid.py +0 -0
  220. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/version.py +0 -0
  221. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/warnings.py +0 -0
  222. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/whenever.py +0 -0
  223. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/zipfile.py +0 -0
  224. {dycw_utilities-0.108.2 → dycw_utilities-0.108.4}/src/utilities/zoneinfo.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.108.2
3
+ Version: 0.108.4
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -82,7 +82,7 @@ Requires-Dist: asyncpg<0.31,>=0.30.0; extra == 'zzz-test-hypothesis'
82
82
  Requires-Dist: greenlet<3.3,>=3.2.0; extra == 'zzz-test-hypothesis'
83
83
  Requires-Dist: hypothesis<6.132,>=6.131.6; extra == 'zzz-test-hypothesis'
84
84
  Requires-Dist: luigi<3.7,>=3.6.0; extra == 'zzz-test-hypothesis'
85
- Requires-Dist: numpy<2.3,>=2.2.4; extra == 'zzz-test-hypothesis'
85
+ Requires-Dist: numpy<2.3,>=2.2.5; extra == 'zzz-test-hypothesis'
86
86
  Requires-Dist: pathvalidate<3.3,>=3.2.3; extra == 'zzz-test-hypothesis'
87
87
  Requires-Dist: redis<5.3,>=5.2.1; extra == 'zzz-test-hypothesis'
88
88
  Requires-Dist: sqlalchemy<2.1,>=2.0.40; extra == 'zzz-test-hypothesis'
@@ -112,14 +112,14 @@ Provides-Extra: zzz-test-luigi
112
112
  Requires-Dist: luigi<3.7,>=3.6.0; extra == 'zzz-test-luigi'
113
113
  Requires-Dist: whenever<0.8,>=0.7.3; extra == 'zzz-test-luigi'
114
114
  Provides-Extra: zzz-test-math
115
- Requires-Dist: numpy<2.3,>=2.2.4; extra == 'zzz-test-math'
115
+ Requires-Dist: numpy<2.3,>=2.2.5; extra == 'zzz-test-math'
116
116
  Provides-Extra: zzz-test-memory-profiler
117
117
  Requires-Dist: memory-profiler<0.62,>=0.61.0; extra == 'zzz-test-memory-profiler'
118
118
  Provides-Extra: zzz-test-modules
119
119
  Provides-Extra: zzz-test-more-itertools
120
120
  Requires-Dist: more-itertools<10.7,>=10.6.0; extra == 'zzz-test-more-itertools'
121
121
  Provides-Extra: zzz-test-numpy
122
- Requires-Dist: numpy<2.3,>=2.2.4; extra == 'zzz-test-numpy'
122
+ Requires-Dist: numpy<2.3,>=2.2.5; extra == 'zzz-test-numpy'
123
123
  Provides-Extra: zzz-test-operator
124
124
  Requires-Dist: polars-lts-cpu<1.28,>=1.27.1; extra == 'zzz-test-operator'
125
125
  Requires-Dist: whenever<0.8,>=0.7.3; extra == 'zzz-test-operator'
@@ -177,7 +177,7 @@ Requires-Dist: scipy<1.16,>=1.15.2; extra == 'zzz-test-scipy'
177
177
  Provides-Extra: zzz-test-sentinel
178
178
  Provides-Extra: zzz-test-shelve
179
179
  Provides-Extra: zzz-test-slack-sdk
180
- Requires-Dist: aiohttp<3.12,>=3.11.16; extra == 'zzz-test-slack-sdk'
180
+ Requires-Dist: aiohttp<3.12,>=3.11.17; extra == 'zzz-test-slack-sdk'
181
181
  Requires-Dist: slack-sdk<3.36,>=3.35.0; extra == 'zzz-test-slack-sdk'
182
182
  Provides-Extra: zzz-test-socket
183
183
  Provides-Extra: zzz-test-sqlalchemy
@@ -8,7 +8,7 @@ requires = ["hatchling"]
8
8
  # dependency groups
9
9
  [dependency-groups]
10
10
  dev = [
11
- "aiohttp >= 3.11.16, < 3.12", # for slack
11
+ "aiohttp >= 3.11.17, < 3.12", # for slack
12
12
  "aiosqlite >= 0.21.0, < 0.22",
13
13
  "altair >= 5.5.0, < 5.6",
14
14
  "astor >= 0.8.1, < 0.9",
@@ -35,7 +35,7 @@ dev = [
35
35
  "more-itertools >= 10.6.0, < 10.7",
36
36
  "nest-asyncio >= 1.6.0, < 1.7", # for sqlalchemy async
37
37
  "nox >= 2025.2.9, < 2025.3",
38
- "numpy >= 2.2.4, < 2.3",
38
+ "numpy >= 2.2.5, < 2.3",
39
39
  "optuna >= 4.3.0, < 4.4",
40
40
  "orjson >= 3.10.16, < 3.11",
41
41
  "pathvalidate >= 3.2.3, < 3.3",
@@ -89,7 +89,7 @@ dependencies = [
89
89
  name = "dycw-utilities"
90
90
  readme = "README.md"
91
91
  requires-python = ">= 3.12"
92
- version = "0.108.2"
92
+ version = "0.108.4"
93
93
 
94
94
  [project.optional-dependencies]
95
95
  test = [
@@ -173,7 +173,7 @@ zzz-test-hypothesis = [
173
173
  "greenlet >= 3.2.0, < 3.3", # for sqlalchemy async
174
174
  "hypothesis >= 6.131.6, < 6.132",
175
175
  "luigi >= 3.6.0, < 3.7",
176
- "numpy >= 2.2.4, < 2.3",
176
+ "numpy >= 2.2.5, < 2.3",
177
177
  "pathvalidate >= 3.2.3, < 3.3",
178
178
  "redis >= 5.2.1, < 5.3",
179
179
  "sqlalchemy >= 2.0.40, < 2.1",
@@ -205,11 +205,11 @@ zzz-test-luigi = [
205
205
  "luigi >= 3.6.0, < 3.7",
206
206
  "whenever >= 0.7.3, < 0.8",
207
207
  ]
208
- zzz-test-math = ["numpy >= 2.2.4, < 2.3"]
208
+ zzz-test-math = ["numpy >= 2.2.5, < 2.3"]
209
209
  zzz-test-memory-profiler = ["memory-profiler >= 0.61.0, < 0.62"]
210
210
  zzz-test-modules = []
211
211
  zzz-test-more-itertools = ["more-itertools >= 10.6.0, < 10.7"]
212
- zzz-test-numpy = ["numpy >= 2.2.4, < 2.3"]
212
+ zzz-test-numpy = ["numpy >= 2.2.5, < 2.3"]
213
213
  zzz-test-operator = [
214
214
  "polars-lts-cpu >= 1.27.1, < 1.28",
215
215
  "whenever >= 0.7.3, < 0.8",
@@ -268,7 +268,7 @@ zzz-test-scipy = ["scipy >= 1.15.2, < 1.16"]
268
268
  zzz-test-sentinel = []
269
269
  zzz-test-shelve = []
270
270
  zzz-test-slack-sdk = [
271
- "aiohttp >= 3.11.16, < 3.12", # for slack
271
+ "aiohttp >= 3.11.17, < 3.12", # for slack
272
272
  "slack-sdk >= 3.35.0, < 3.36",
273
273
  ]
274
274
  zzz-test-socket = []
@@ -332,7 +332,7 @@ zzz-test-zoneinfo = [
332
332
  # bump-my-version
333
333
  [tool.bumpversion]
334
334
  allow_dirty = true
335
- current_version = "0.108.2"
335
+ current_version = "0.108.4"
336
336
 
337
337
  [[tool.bumpversion.files]]
338
338
  filename = "src/utilities/__init__.py"
@@ -45,12 +45,17 @@ from utilities.dataclasses import (
45
45
  YieldFieldsError,
46
46
  _MappingToDataclassCaseInsensitiveNonUniqueError,
47
47
  _MappingToDataclassEmptyError,
48
+ _TextToDataClassGetFieldEmptyError,
49
+ _TextToDataClassGetFieldNonUniqueError,
50
+ _TextToDataClassParseValueError,
51
+ _TextToDataClassSplitKeyValuePairError,
48
52
  _YieldFieldsClass,
49
53
  _YieldFieldsInstance,
50
54
  dataclass_repr,
51
55
  dataclass_to_dict,
52
56
  mapping_to_dataclass,
53
57
  replace_non_sentinel,
58
+ text_to_dataclass,
54
59
  yield_fields,
55
60
  )
56
61
  from utilities.functions import get_class_name
@@ -65,6 +70,43 @@ from utilities.typing import get_args, is_list_type, is_literal_type, is_optiona
65
70
  TruthLit = Literal["true", "false"] # in 3.12, use type TruthLit = ...
66
71
 
67
72
 
73
+ class TestDataClassRepr:
74
+ def test_overriding_repr(self) -> None:
75
+ @dataclass(kw_only=True, slots=True)
76
+ class Example(DataClassFutureIntDefault):
77
+ @override
78
+ def __repr__(self) -> str:
79
+ return dataclass_repr(self)
80
+
81
+ obj = Example()
82
+ result = repr(obj)
83
+ expected = "Example()"
84
+ assert result == expected
85
+
86
+ def test_overriding_repr_defaults(self) -> None:
87
+ @dataclass(kw_only=True)
88
+ class Example(DataClassFutureIntDefault):
89
+ @override
90
+ def __repr__(self) -> str:
91
+ return dataclass_repr(self, defaults=True)
92
+
93
+ obj = Example()
94
+ result = repr(obj)
95
+ expected = "Example(int_=0)"
96
+ assert result == expected
97
+
98
+ @given(x=integers())
99
+ def test_non_repr_field(self, *, x: int) -> None:
100
+ @dataclass(kw_only=True, slots=True)
101
+ class Example:
102
+ x: int = field(default=0, repr=False)
103
+
104
+ obj = Example(x=x)
105
+ result = dataclass_repr(obj)
106
+ expected = "Example()"
107
+ assert result == expected
108
+
109
+
68
110
  class TestDataclassToDictAndDataclassRepr:
69
111
  @given(x=integers(), defaults=booleans())
70
112
  def test_field_without_defaults(self, *, x: int, defaults: bool) -> None:
@@ -328,31 +370,51 @@ class TestReplaceNonSentinel:
328
370
  assert obj.int_ == 1
329
371
 
330
372
 
331
- class TestReprWithoutDefaults:
332
- def test_overriding_repr(self) -> None:
333
- @dataclass(kw_only=True, slots=True)
334
- class Example:
335
- x: int = 0
336
-
337
- @override
338
- def __repr__(self) -> str:
339
- return dataclass_repr(self)
373
+ class TestTextToDataClass:
374
+ @given(int_=integers())
375
+ def test_main_text(self, *, int_: int) -> None:
376
+ result = text_to_dataclass(f"int_={int_}", DataClassFutureInt)
377
+ expected = DataClassFutureInt(int_=int_)
378
+ assert result == expected
340
379
 
341
- obj = Example()
342
- result = repr(obj)
343
- expected = "Example()"
380
+ @given(int_=integers())
381
+ def test_main_mapping(self, *, int_: int) -> None:
382
+ result = text_to_dataclass({"int_": str(int_)}, DataClassFutureInt)
383
+ expected = DataClassFutureInt(int_=int_)
344
384
  assert result == expected
345
385
 
346
- @given(x=integers())
347
- def test_non_repr_field(self, *, x: int) -> None:
348
- @dataclass(kw_only=True, slots=True)
386
+ def test_error_split_key_value_pair(self) -> None:
387
+ with raises(
388
+ _TextToDataClassSplitKeyValuePairError,
389
+ match="Unable to construct 'DataClassFutureInt'; failed to split key-value pair 'keyvalue'",
390
+ ):
391
+ _ = text_to_dataclass("keyvalue", DataClassFutureInt)
392
+
393
+ def test_error_get_field_empty(self) -> None:
394
+ with raises(
395
+ _TextToDataClassGetFieldEmptyError,
396
+ match=r"Dataclass 'DataClassFutureInt' does not contain any field starting with 'k' \(modulo case\)",
397
+ ):
398
+ _ = text_to_dataclass("k=value", DataClassFutureInt)
399
+
400
+ def test_error_get_field_non_unique(self) -> None:
401
+ @dataclass(order=True, unsafe_hash=True, kw_only=True)
349
402
  class Example:
350
- x: int = field(default=0, repr=False)
403
+ int1: int
404
+ int2: int
351
405
 
352
- obj = Example(x=x)
353
- result = dataclass_repr(obj)
354
- expected = "Example()"
355
- assert result == expected
406
+ with raises(
407
+ _TextToDataClassGetFieldNonUniqueError,
408
+ match=r"Dataclass 'Example' must contain exactly one field starting with 'int' \(modulo case\); got 'int1', 'int2' and perhaps more",
409
+ ):
410
+ _ = text_to_dataclass("int=value", Example)
411
+
412
+ def test_error_parse_value(self) -> None:
413
+ with raises(
414
+ _TextToDataClassParseValueError,
415
+ match="Unable to construct 'DataClassFutureInt'; unable to parse field 'int_' of type <class 'int'>; got 'invalid'",
416
+ ):
417
+ _ = text_to_dataclass("int_=invalid", DataClassFutureInt)
356
418
 
357
419
 
358
420
  class TestYieldFields:
@@ -5,6 +5,7 @@ from dataclasses import dataclass
5
5
  from functools import cache, cached_property, lru_cache, partial, wraps
6
6
  from itertools import chain
7
7
  from operator import neg
8
+ from pathlib import Path
8
9
  from types import NoneType
9
10
  from typing import TYPE_CHECKING, Any, ClassVar, ParamSpec, TypeVar, cast
10
11
 
@@ -37,6 +38,7 @@ from utilities.functions import (
37
38
  EnsureMemberError,
38
39
  EnsureNotNoneError,
39
40
  EnsureNumberError,
41
+ EnsurePathError,
40
42
  EnsureSizedError,
41
43
  EnsureSizedNotStrError,
42
44
  EnsureStrError,
@@ -56,6 +58,7 @@ from utilities.functions import (
56
58
  ensure_member,
57
59
  ensure_not_none,
58
60
  ensure_number,
61
+ ensure_path,
59
62
  ensure_sized,
60
63
  ensure_sized_not_str,
61
64
  ensure_str,
@@ -336,6 +339,24 @@ class TestEnsureNumber:
336
339
  _ = ensure_number(sentinel, nullable=nullable)
337
340
 
338
341
 
342
+ class TestEnsurePath:
343
+ @given(case=sampled_from([(Path.home(), False), (Path.home(), True), (None, True)]))
344
+ def test_main(self, *, case: tuple[int | None, bool]) -> None:
345
+ obj, nullable = case
346
+ _ = ensure_path(obj, nullable=nullable)
347
+
348
+ @given(
349
+ case=sampled_from([
350
+ (False, "Object '.*' of type '.*' must be a Path"),
351
+ (True, "Object '.*' of type '.*' must be a Path or None"),
352
+ ])
353
+ )
354
+ def test_error(self, *, case: tuple[bool, str]) -> None:
355
+ nullable, match = case
356
+ with raises(EnsurePathError, match=match):
357
+ _ = ensure_path(sentinel, nullable=nullable)
358
+
359
+
339
360
  class TestEnsureSized:
340
361
  @given(obj=sampled_from([[], (), ""]))
341
362
  def test_main(self, *, obj: Any) -> None:
@@ -12,6 +12,7 @@ from hypothesis.strategies import booleans, dates, floats, integers, sampled_fro
12
12
  from pytest import raises
13
13
 
14
14
  from tests.test_operator import TruthEnum
15
+ from utilities.functions import ensure_path
15
16
  from utilities.hypothesis import (
16
17
  local_datetimes,
17
18
  paths,
@@ -101,6 +102,13 @@ class TestParseText:
101
102
  result = parse_text(Path, text)
102
103
  assert result == path
103
104
 
105
+ @given(path=paths())
106
+ def test_path_expanded(self, *, path: Path) -> None:
107
+ path_use = Path("~", path)
108
+ text = str(path_use)
109
+ result = ensure_path(parse_text(Path, text))
110
+ assert result == result.expanduser()
111
+
104
112
  def test_sentinel(self) -> None:
105
113
  text = str(sentinel)
106
114
  result = parse_text(Sentinel, text)
@@ -0,0 +1,148 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from dataclasses import dataclass
5
+ from re import DOTALL
6
+ from typing import TYPE_CHECKING
7
+
8
+ from hypothesis import given
9
+ from hypothesis.strategies import DataObject, booleans, data, integers, sampled_from
10
+ from pytest import raises
11
+
12
+ from utilities.errors import ImpossibleCaseError
13
+ from utilities.hypothesis import git_repos, settings_with_reduced_examples, text_ascii
14
+ from utilities.os import temp_environ
15
+ from utilities.python_dotenv import (
16
+ _LoadSettingsDuplicateKeysError,
17
+ _LoadSettingsEmptyError,
18
+ _LoadSettingsFileNotFoundError,
19
+ _LoadSettingsParseTextError,
20
+ load_settings,
21
+ )
22
+
23
+ if TYPE_CHECKING:
24
+ from pathlib import Path
25
+
26
+
27
+ class TestLoadSettings:
28
+ @given(
29
+ data=data(),
30
+ root=git_repos(),
31
+ key_file=sampled_from(["key", "KEY"]),
32
+ value_file=text_ascii(),
33
+ use_env=booleans(),
34
+ )
35
+ @settings_with_reduced_examples()
36
+ def test_main(
37
+ self,
38
+ *,
39
+ data: DataObject,
40
+ root: Path,
41
+ key_file: str,
42
+ value_file: str,
43
+ use_env: bool,
44
+ ) -> None:
45
+ with root.joinpath(".env").open(mode="w") as fh:
46
+ _ = fh.write(f"{key_file} = {value_file}\n")
47
+
48
+ @dataclass(kw_only=True, slots=True)
49
+ class SettingsLower:
50
+ key: str
51
+
52
+ @dataclass(kw_only=True, slots=True)
53
+ class SettingsUpper:
54
+ KEY: str
55
+
56
+ SettingsUse = data.draw(sampled_from([SettingsLower, SettingsUpper])) # noqa: N806
57
+ if use_env:
58
+ key_env = data.draw(sampled_from(["key", "KEY"]))
59
+ value_env = data.draw(text_ascii())
60
+ with temp_environ({key_env: value_env}):
61
+ settings = load_settings(SettingsUse, cwd=root)
62
+ exp_value = value_env
63
+ else:
64
+ settings = load_settings(SettingsUse, cwd=root)
65
+ exp_value = value_file
66
+
67
+ if SettingsUse is SettingsLower:
68
+ expected = SettingsLower(key=exp_value)
69
+ elif SettingsUse is SettingsUpper:
70
+ expected = SettingsUpper(KEY=exp_value)
71
+ else:
72
+ raise ImpossibleCaseError(case=[f"{SettingsUse=}"])
73
+ assert settings == expected
74
+
75
+ @given(root=git_repos(), value=text_ascii())
76
+ @settings_with_reduced_examples()
77
+ def test_file_extra_key(self, *, root: Path, value: str) -> None:
78
+ @dataclass(kw_only=True, slots=True)
79
+ class Settings:
80
+ key: str
81
+
82
+ with root.joinpath(".env").open(mode="w") as fh:
83
+ _ = fh.write(f"key = {value}\n")
84
+ _ = fh.write(f"other = {value}\n")
85
+
86
+ settings = load_settings(Settings, cwd=root)
87
+ expected = Settings(key=value)
88
+ assert settings == expected
89
+
90
+ @given(root=git_repos())
91
+ @settings_with_reduced_examples()
92
+ def test_error_file_not_found(self, *, root: Path) -> None:
93
+ @dataclass(kw_only=True, slots=True)
94
+ class Settings:
95
+ KEY: str
96
+
97
+ with raises(_LoadSettingsFileNotFoundError, match=r"Path '.*' must exist"):
98
+ _ = load_settings(Settings, cwd=root)
99
+
100
+ @given(root=git_repos(), value=integers())
101
+ @settings_with_reduced_examples()
102
+ def test_error_duplicate_keys(self, *, root: Path, value: int) -> None:
103
+ @dataclass(kw_only=True, slots=True)
104
+ class Settings:
105
+ key: str
106
+
107
+ with root.joinpath(".env").open(mode="w") as fh:
108
+ _ = fh.write(f"key = {value}\n")
109
+ _ = fh.write(f"KEY = {value}\n")
110
+
111
+ with raises(
112
+ _LoadSettingsDuplicateKeysError,
113
+ match=re.compile(
114
+ r"Mapping .* keys must not contain duplicates \(modulo case\); got .*",
115
+ flags=DOTALL,
116
+ ),
117
+ ):
118
+ _ = load_settings(Settings, cwd=root)
119
+
120
+ @given(root=git_repos())
121
+ @settings_with_reduced_examples()
122
+ def test_error_field_missing(self, *, root: Path) -> None:
123
+ @dataclass(kw_only=True, slots=True)
124
+ class Settings:
125
+ key: str
126
+
127
+ root.joinpath(".env").touch()
128
+
129
+ with raises(
130
+ _LoadSettingsEmptyError, match=r"Field 'key' must exist \(modulo case\)"
131
+ ):
132
+ _ = load_settings(Settings, cwd=root)
133
+
134
+ @given(root=git_repos())
135
+ @settings_with_reduced_examples()
136
+ def test_error_parse_text(self, *, root: Path) -> None:
137
+ @dataclass(kw_only=True, slots=True)
138
+ class Settings:
139
+ key: int
140
+
141
+ with root.joinpath(".env").open(mode="w") as fh:
142
+ _ = fh.write("key = '...'\n")
143
+
144
+ with raises(
145
+ _LoadSettingsParseTextError,
146
+ match=r"Unable to parse field 'key' of type <class 'int'>; got '...'",
147
+ ):
148
+ _ = load_settings(Settings, cwd=root)
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.108.2"
3
+ __version__ = "0.108.4"