dycw-utilities 0.108.2__tar.gz → 0.108.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 (224) hide show
  1. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/PKG-INFO +1 -1
  2. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/pyproject.toml +2 -2
  3. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_functions.py +21 -0
  4. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_parse.py +8 -0
  5. dycw_utilities-0.108.3/src/tests/test_python_dotenv.py +148 -0
  6. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/__init__.py +1 -1
  7. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/functions.py +28 -0
  8. dycw_utilities-0.108.3/src/utilities/python_dotenv.py +116 -0
  9. dycw_utilities-0.108.2/src/tests/test_python_dotenv.py +0 -452
  10. dycw_utilities-0.108.2/src/utilities/python_dotenv.py +0 -259
  11. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/.gitignore +0 -0
  12. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/LICENSE +0 -0
  13. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/README.md +0 -0
  14. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/__init__.py +0 -0
  15. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/conftest.py +0 -0
  16. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/modules/__init__.py +0 -0
  17. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/modules/package_missing/__init__.py +0 -0
  18. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/modules/package_missing/module.py +0 -0
  19. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/modules/package_with/__init__.py +0 -0
  20. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/modules/package_with/outer_1.py +0 -0
  21. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/modules/package_with/outer_2.py +0 -0
  22. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/modules/package_with/subpackage/__init__.py +0 -0
  23. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/modules/package_with/subpackage/inner_1.py +0 -0
  24. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/modules/package_with/subpackage/inner_2.py +0 -0
  25. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/modules/package_with/subpackage/inner_3.py +0 -0
  26. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/modules/package_without/__init__.py +0 -0
  27. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/modules/package_without/module_1.py +0 -0
  28. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/modules/package_without/module_2.py +0 -0
  29. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/modules/standalone.py +0 -0
  30. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/modules/with_imports.py +0 -0
  31. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -0
  32. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -0
  33. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -0
  34. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -0
  35. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -0
  36. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -0
  37. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -0
  38. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -0
  39. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/scripts/__init__.py +0 -0
  40. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/scripts/test_async_service/__init__.py +0 -0
  41. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/scripts/test_async_service/__main__.py +0 -0
  42. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/scripts/test_async_service/run.sh +0 -0
  43. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/scripts/test_queue_processor/__init__.py +0 -0
  44. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/scripts/test_queue_processor/__main__.py +0 -0
  45. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/scripts/test_queue_processor/run.sh +0 -0
  46. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_altair.py +0 -0
  47. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_astor.py +0 -0
  48. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_asyncio.py +0 -0
  49. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_atomicwrites.py +0 -0
  50. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_atools.py +0 -0
  51. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_cachetools.py +0 -0
  52. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_click.py +0 -0
  53. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_concurrent.py +0 -0
  54. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_contextlib.py +0 -0
  55. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_contextvars.py +0 -0
  56. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_cryptography.py +0 -0
  57. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_cvxpy.py +0 -0
  58. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_dataclasses.py +0 -0
  59. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_datetime.py +0 -0
  60. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_enum.py +0 -0
  61. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_errors.py +0 -0
  62. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_eventkit.py +0 -0
  63. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_fastapi.py +0 -0
  64. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_fpdf2.py +0 -0
  65. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_functools.py +0 -0
  66. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_getpass.py +0 -0
  67. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_git.py +0 -0
  68. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_hashlib.py +0 -0
  69. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_http.py +0 -0
  70. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_hypothesis.py +0 -0
  71. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_ipython.py +0 -0
  72. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_iterables.py +0 -0
  73. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_jupyter.py +0 -0
  74. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_logging.py +0 -0
  75. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_loguru.py +0 -0
  76. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_luigi.py +0 -0
  77. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_math.py +0 -0
  78. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_memory_profiler.py +0 -0
  79. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_modules.py +0 -0
  80. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_more_itertools.py +0 -0
  81. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_numpy.py +0 -0
  82. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_operator.py +0 -0
  83. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_optuna.py +0 -0
  84. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_orjson.py +0 -0
  85. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_os.py +0 -0
  86. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_pathlib.py +0 -0
  87. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_period.py +0 -0
  88. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_pickle.py +0 -0
  89. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_platform.py +0 -0
  90. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_polars.py +0 -0
  91. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_pqdm.py +0 -0
  92. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_pydantic.py +0 -0
  93. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_pyinstrument.py +0 -0
  94. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_pyrsistent.py +0 -0
  95. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_pytest.py +0 -0
  96. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_pytest_regressions.py +0 -0
  97. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_random.py +0 -0
  98. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_re.py +0 -0
  99. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_redis.py +0 -0
  100. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_reprlib.py +0 -0
  101. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_rich.py +0 -0
  102. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_scipy.py +0 -0
  103. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_sentinel.py +0 -0
  104. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_shelve.py +0 -0
  105. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_slack_sdk.py +0 -0
  106. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_socket.py +0 -0
  107. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_sqlalchemy.py +0 -0
  108. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_sqlalchemy_polars.py +0 -0
  109. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_streamlit.py +0 -0
  110. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_sys.py +0 -0
  111. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_tempfile.py +0 -0
  112. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_tenacity.py +0 -0
  113. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_text.py +0 -0
  114. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_threading.py +0 -0
  115. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_timer.py +0 -0
  116. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_traceback.py +0 -0
  117. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_traceback_funcs/__init__.py +0 -0
  118. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_traceback_funcs/chain.py +0 -0
  119. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_traceback_funcs/decorated_async.py +0 -0
  120. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_traceback_funcs/decorated_sync.py +0 -0
  121. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_traceback_funcs/error_bind.py +0 -0
  122. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_traceback_funcs/many.py +0 -0
  123. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_traceback_funcs/one.py +0 -0
  124. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_traceback_funcs/recursive.py +0 -0
  125. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_traceback_funcs/task_group_one.py +0 -0
  126. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_traceback_funcs/task_group_two.py +0 -0
  127. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_traceback_funcs/two.py +0 -0
  128. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_traceback_funcs/untraced.py +0 -0
  129. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_types.py +0 -0
  130. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_typing.py +0 -0
  131. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_typing_funcs/__init__.py +0 -0
  132. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_typing_funcs/no_future.py +0 -0
  133. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_typing_funcs/with_future.py +0 -0
  134. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_tzdata.py +0 -0
  135. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_tzlocal.py +0 -0
  136. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_uuid.py +0 -0
  137. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_version.py +0 -0
  138. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_warnings.py +0 -0
  139. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_whenever.py +0 -0
  140. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_zipfile.py +0 -0
  141. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/tests/test_zoneinfo.py +0 -0
  142. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/altair.py +0 -0
  143. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/astor.py +0 -0
  144. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/asyncio.py +0 -0
  145. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/atomicwrites.py +0 -0
  146. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/atools.py +0 -0
  147. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/cachetools.py +0 -0
  148. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/click.py +0 -0
  149. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/concurrent.py +0 -0
  150. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/contextlib.py +0 -0
  151. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/contextvars.py +0 -0
  152. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/cryptography.py +0 -0
  153. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/cvxpy.py +0 -0
  154. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/dataclasses.py +0 -0
  155. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/datetime.py +0 -0
  156. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/enum.py +0 -0
  157. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/errors.py +0 -0
  158. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/eventkit.py +0 -0
  159. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/fastapi.py +0 -0
  160. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/fpdf2.py +0 -0
  161. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/functools.py +0 -0
  162. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/getpass.py +0 -0
  163. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/git.py +0 -0
  164. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/hashlib.py +0 -0
  165. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/http.py +0 -0
  166. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/hypothesis.py +0 -0
  167. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/ipython.py +0 -0
  168. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/iterables.py +0 -0
  169. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/jupyter.py +0 -0
  170. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/logging.py +0 -0
  171. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/loguru.py +0 -0
  172. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/luigi.py +0 -0
  173. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/math.py +0 -0
  174. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/memory_profiler.py +0 -0
  175. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/modules.py +0 -0
  176. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/more_itertools.py +0 -0
  177. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/numpy.py +0 -0
  178. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/operator.py +0 -0
  179. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/optuna.py +0 -0
  180. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/orjson.py +0 -0
  181. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/os.py +0 -0
  182. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/parse.py +0 -0
  183. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/pathlib.py +0 -0
  184. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/period.py +0 -0
  185. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/pickle.py +0 -0
  186. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/platform.py +0 -0
  187. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/polars.py +0 -0
  188. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/pqdm.py +0 -0
  189. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/py.typed +0 -0
  190. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/pydantic.py +0 -0
  191. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/pyinstrument.py +0 -0
  192. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/pyrsistent.py +0 -0
  193. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/pytest.py +0 -0
  194. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/pytest_regressions.py +0 -0
  195. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/random.py +0 -0
  196. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/re.py +0 -0
  197. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/redis.py +0 -0
  198. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/reprlib.py +0 -0
  199. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/rich.py +0 -0
  200. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/scipy.py +0 -0
  201. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/sentinel.py +0 -0
  202. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/shelve.py +0 -0
  203. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/slack_sdk.py +0 -0
  204. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/socket.py +0 -0
  205. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/sqlalchemy.py +0 -0
  206. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/sqlalchemy_polars.py +0 -0
  207. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/streamlit.py +0 -0
  208. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/sys.py +0 -0
  209. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/tempfile.py +0 -0
  210. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/tenacity.py +0 -0
  211. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/text.py +0 -0
  212. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/threading.py +0 -0
  213. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/timer.py +0 -0
  214. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/traceback.py +0 -0
  215. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/types.py +0 -0
  216. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/typing.py +0 -0
  217. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/tzdata.py +0 -0
  218. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/tzlocal.py +0 -0
  219. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/uuid.py +0 -0
  220. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/version.py +0 -0
  221. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/warnings.py +0 -0
  222. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/whenever.py +0 -0
  223. {dycw_utilities-0.108.2 → dycw_utilities-0.108.3}/src/utilities/zipfile.py +0 -0
  224. {dycw_utilities-0.108.2 → dycw_utilities-0.108.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.108.2
3
+ Version: 0.108.3
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -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.3"
93
93
 
94
94
  [project.optional-dependencies]
95
95
  test = [
@@ -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.3"
336
336
 
337
337
  [[tool.bumpversion.files]]
338
338
  filename = "src/utilities/__init__.py"
@@ -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.3"
@@ -5,6 +5,7 @@ from collections.abc import Callable, Iterable, Iterator, Sequence
5
5
  from dataclasses import asdict, dataclass, is_dataclass
6
6
  from functools import _lru_cache_wrapper, cached_property, partial, reduce, wraps
7
7
  from inspect import getattr_static
8
+ from pathlib import Path
8
9
  from re import findall
9
10
  from types import (
10
11
  BuiltinFunctionType,
@@ -405,6 +406,31 @@ class EnsureNumberError(Exception):
405
406
  ##
406
407
 
407
408
 
409
+ @overload
410
+ def ensure_path(obj: Any, /, *, nullable: bool) -> Path | None: ...
411
+ @overload
412
+ def ensure_path(obj: Any, /, *, nullable: Literal[False] = False) -> Path: ...
413
+ def ensure_path(obj: Any, /, *, nullable: bool = False) -> Path | None:
414
+ """Ensure an object is a Path."""
415
+ try:
416
+ return ensure_class(obj, Path, nullable=nullable)
417
+ except EnsureClassError as error:
418
+ raise EnsurePathError(obj=error.obj, nullable=nullable) from None
419
+
420
+
421
+ @dataclass(kw_only=True, slots=True)
422
+ class EnsurePathError(Exception):
423
+ obj: Any
424
+ nullable: bool
425
+
426
+ @override
427
+ def __str__(self) -> str:
428
+ return _make_error_msg(self.obj, "a Path", nullable=self.nullable)
429
+
430
+
431
+ ##
432
+
433
+
408
434
  def ensure_sized(obj: Any, /) -> Sized:
409
435
  """Ensure an object is sized."""
410
436
  if is_sized(obj):
@@ -985,6 +1011,7 @@ __all__ = [
985
1011
  "EnsureMemberError",
986
1012
  "EnsureNotNoneError",
987
1013
  "EnsureNumberError",
1014
+ "EnsurePathError",
988
1015
  "EnsureSizedError",
989
1016
  "EnsureSizedNotStrError",
990
1017
  "EnsureStrError",
@@ -1004,6 +1031,7 @@ __all__ = [
1004
1031
  "ensure_member",
1005
1032
  "ensure_not_none",
1006
1033
  "ensure_number",
1034
+ "ensure_path",
1007
1035
  "ensure_sized",
1008
1036
  "ensure_sized_not_str",
1009
1037
  "ensure_str",
@@ -0,0 +1,116 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from functools import partial
5
+ from os import environ
6
+ from typing import TYPE_CHECKING, Any, override
7
+
8
+ from dotenv import dotenv_values
9
+
10
+ from utilities.dataclasses import (
11
+ _MappingToDataclassEmptyError,
12
+ _YieldFieldsClass,
13
+ mapping_to_dataclass,
14
+ )
15
+ from utilities.git import get_repo_root
16
+ from utilities.iterables import MergeStrMappingsError, merge_str_mappings
17
+ from utilities.parse import ParseTextError, parse_text
18
+ from utilities.pathlib import PWD
19
+ from utilities.reprlib import get_repr
20
+
21
+ if TYPE_CHECKING:
22
+ from collections.abc import Mapping
23
+ from pathlib import Path
24
+
25
+ from utilities.types import PathLike, StrMapping, TDataclass
26
+
27
+
28
+ def load_settings(
29
+ cls: type[TDataclass],
30
+ /,
31
+ *,
32
+ cwd: PathLike = PWD,
33
+ globalns: StrMapping | None = None,
34
+ localns: StrMapping | None = None,
35
+ ) -> TDataclass:
36
+ """Load a set of settings from the `.env` file."""
37
+ path = get_repo_root(cwd=cwd).joinpath(".env")
38
+ if not path.exists():
39
+ raise _LoadSettingsFileNotFoundError(path=path) from None
40
+ maybe_values_dotenv = dotenv_values(path)
41
+ try:
42
+ maybe_values = merge_str_mappings(maybe_values_dotenv, environ)
43
+ except MergeStrMappingsError as error:
44
+ raise _LoadSettingsDuplicateKeysError(
45
+ path=path, values=error.mapping, counts=error.counts
46
+ ) from None
47
+ values = {k: v for k, v in maybe_values.items() if v is not None}
48
+ try:
49
+ return mapping_to_dataclass(
50
+ cls,
51
+ values,
52
+ globalns=globalns,
53
+ localns=localns,
54
+ post=partial(_load_settings_post, path=path, values=values),
55
+ )
56
+ except _MappingToDataclassEmptyError as error:
57
+ raise _LoadSettingsEmptyError(
58
+ path=path, values=error.mapping, field=error.field
59
+ ) from None
60
+
61
+
62
+ def _load_settings_post(
63
+ field: _YieldFieldsClass[Any], text: str, /, *, path: Path, values: StrMapping
64
+ ) -> Any:
65
+ try:
66
+ return parse_text(field.type_, text)
67
+ except ParseTextError:
68
+ raise _LoadSettingsParseTextError(
69
+ path=path, values=values, field=field, text=text
70
+ ) from None
71
+
72
+
73
+ @dataclass(kw_only=True, slots=True)
74
+ class LoadSettingsError(Exception):
75
+ path: Path
76
+
77
+
78
+ @dataclass(kw_only=True, slots=True)
79
+ class _LoadSettingsDuplicateKeysError(LoadSettingsError):
80
+ values: StrMapping
81
+ counts: Mapping[str, int]
82
+
83
+ @override
84
+ def __str__(self) -> str:
85
+ return f"Mapping {get_repr(dict(self.values))} keys must not contain duplicates (modulo case); got {get_repr(self.counts)}"
86
+
87
+
88
+ @dataclass(kw_only=True, slots=True)
89
+ class _LoadSettingsEmptyError(LoadSettingsError):
90
+ values: StrMapping
91
+ field: str
92
+
93
+ @override
94
+ def __str__(self) -> str:
95
+ return f"Field {self.field!r} must exist (modulo case)"
96
+
97
+
98
+ @dataclass(kw_only=True, slots=True)
99
+ class _LoadSettingsFileNotFoundError(LoadSettingsError):
100
+ @override
101
+ def __str__(self) -> str:
102
+ return f"Path {str(self.path)!r} must exist"
103
+
104
+
105
+ @dataclass(kw_only=True, slots=True)
106
+ class _LoadSettingsParseTextError(LoadSettingsError):
107
+ values: StrMapping
108
+ field: _YieldFieldsClass[Any]
109
+ text: str
110
+
111
+ @override
112
+ def __str__(self) -> str:
113
+ return f"Unable to parse field {self.field.name!r} of type {self.field.type_!r}; got {self.text!r}"
114
+
115
+
116
+ __all__ = ["LoadSettingsError", "load_settings"]