dycw-utilities 0.108.1__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 (225) hide show
  1. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/PKG-INFO +1 -1
  2. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/pyproject.toml +2 -2
  3. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_functions.py +21 -0
  4. dycw_utilities-0.108.3/src/tests/test_parse.py +242 -0
  5. dycw_utilities-0.108.3/src/tests/test_python_dotenv.py +148 -0
  6. dycw_utilities-0.108.3/src/tests/test_sentinel.py +45 -0
  7. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_text.py +21 -4
  8. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/__init__.py +1 -1
  9. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/functions.py +28 -0
  10. dycw_utilities-0.108.3/src/utilities/parse.py +129 -0
  11. dycw_utilities-0.108.3/src/utilities/python_dotenv.py +116 -0
  12. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/sentinel.py +28 -1
  13. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/text.py +22 -1
  14. dycw_utilities-0.108.1/src/tests/test_python_dotenv.py +0 -452
  15. dycw_utilities-0.108.1/src/tests/test_sentinel.py +0 -22
  16. dycw_utilities-0.108.1/src/utilities/python_dotenv.py +0 -259
  17. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/.gitignore +0 -0
  18. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/LICENSE +0 -0
  19. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/README.md +0 -0
  20. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/__init__.py +0 -0
  21. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/conftest.py +0 -0
  22. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/modules/__init__.py +0 -0
  23. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/modules/package_missing/__init__.py +0 -0
  24. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/modules/package_missing/module.py +0 -0
  25. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/modules/package_with/__init__.py +0 -0
  26. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/modules/package_with/outer_1.py +0 -0
  27. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/modules/package_with/outer_2.py +0 -0
  28. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/modules/package_with/subpackage/__init__.py +0 -0
  29. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/modules/package_with/subpackage/inner_1.py +0 -0
  30. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/modules/package_with/subpackage/inner_2.py +0 -0
  31. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/modules/package_with/subpackage/inner_3.py +0 -0
  32. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/modules/package_without/__init__.py +0 -0
  33. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/modules/package_without/module_1.py +0 -0
  34. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/modules/package_without/module_2.py +0 -0
  35. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/modules/standalone.py +0 -0
  36. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/modules/with_imports.py +0 -0
  37. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -0
  38. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -0
  39. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -0
  40. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -0
  41. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -0
  42. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -0
  43. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -0
  44. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -0
  45. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/scripts/__init__.py +0 -0
  46. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/scripts/test_async_service/__init__.py +0 -0
  47. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/scripts/test_async_service/__main__.py +0 -0
  48. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/scripts/test_async_service/run.sh +0 -0
  49. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/scripts/test_queue_processor/__init__.py +0 -0
  50. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/scripts/test_queue_processor/__main__.py +0 -0
  51. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/scripts/test_queue_processor/run.sh +0 -0
  52. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_altair.py +0 -0
  53. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_astor.py +0 -0
  54. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_asyncio.py +0 -0
  55. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_atomicwrites.py +0 -0
  56. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_atools.py +0 -0
  57. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_cachetools.py +0 -0
  58. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_click.py +0 -0
  59. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_concurrent.py +0 -0
  60. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_contextlib.py +0 -0
  61. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_contextvars.py +0 -0
  62. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_cryptography.py +0 -0
  63. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_cvxpy.py +0 -0
  64. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_dataclasses.py +0 -0
  65. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_datetime.py +0 -0
  66. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_enum.py +0 -0
  67. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_errors.py +0 -0
  68. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_eventkit.py +0 -0
  69. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_fastapi.py +0 -0
  70. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_fpdf2.py +0 -0
  71. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_functools.py +0 -0
  72. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_getpass.py +0 -0
  73. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_git.py +0 -0
  74. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_hashlib.py +0 -0
  75. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_http.py +0 -0
  76. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_hypothesis.py +0 -0
  77. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_ipython.py +0 -0
  78. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_iterables.py +0 -0
  79. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_jupyter.py +0 -0
  80. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_logging.py +0 -0
  81. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_loguru.py +0 -0
  82. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_luigi.py +0 -0
  83. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_math.py +0 -0
  84. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_memory_profiler.py +0 -0
  85. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_modules.py +0 -0
  86. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_more_itertools.py +0 -0
  87. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_numpy.py +0 -0
  88. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_operator.py +0 -0
  89. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_optuna.py +0 -0
  90. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_orjson.py +0 -0
  91. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_os.py +0 -0
  92. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_pathlib.py +0 -0
  93. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_period.py +0 -0
  94. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_pickle.py +0 -0
  95. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_platform.py +0 -0
  96. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_polars.py +0 -0
  97. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_pqdm.py +0 -0
  98. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_pydantic.py +0 -0
  99. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_pyinstrument.py +0 -0
  100. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_pyrsistent.py +0 -0
  101. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_pytest.py +0 -0
  102. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_pytest_regressions.py +0 -0
  103. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_random.py +0 -0
  104. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_re.py +0 -0
  105. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_redis.py +0 -0
  106. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_reprlib.py +0 -0
  107. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_rich.py +0 -0
  108. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_scipy.py +0 -0
  109. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_shelve.py +0 -0
  110. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_slack_sdk.py +0 -0
  111. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_socket.py +0 -0
  112. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_sqlalchemy.py +0 -0
  113. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_sqlalchemy_polars.py +0 -0
  114. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_streamlit.py +0 -0
  115. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_sys.py +0 -0
  116. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_tempfile.py +0 -0
  117. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_tenacity.py +0 -0
  118. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_threading.py +0 -0
  119. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_timer.py +0 -0
  120. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_traceback.py +0 -0
  121. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_traceback_funcs/__init__.py +0 -0
  122. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_traceback_funcs/chain.py +0 -0
  123. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_traceback_funcs/decorated_async.py +0 -0
  124. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_traceback_funcs/decorated_sync.py +0 -0
  125. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_traceback_funcs/error_bind.py +0 -0
  126. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_traceback_funcs/many.py +0 -0
  127. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_traceback_funcs/one.py +0 -0
  128. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_traceback_funcs/recursive.py +0 -0
  129. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_traceback_funcs/task_group_one.py +0 -0
  130. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_traceback_funcs/task_group_two.py +0 -0
  131. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_traceback_funcs/two.py +0 -0
  132. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_traceback_funcs/untraced.py +0 -0
  133. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_types.py +0 -0
  134. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_typing.py +0 -0
  135. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_typing_funcs/__init__.py +0 -0
  136. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_typing_funcs/no_future.py +0 -0
  137. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_typing_funcs/with_future.py +0 -0
  138. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_tzdata.py +0 -0
  139. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_tzlocal.py +0 -0
  140. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_uuid.py +0 -0
  141. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_version.py +0 -0
  142. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_warnings.py +0 -0
  143. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_whenever.py +0 -0
  144. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_zipfile.py +0 -0
  145. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_zoneinfo.py +0 -0
  146. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/altair.py +0 -0
  147. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/astor.py +0 -0
  148. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/asyncio.py +0 -0
  149. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/atomicwrites.py +0 -0
  150. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/atools.py +0 -0
  151. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/cachetools.py +0 -0
  152. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/click.py +0 -0
  153. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/concurrent.py +0 -0
  154. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/contextlib.py +0 -0
  155. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/contextvars.py +0 -0
  156. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/cryptography.py +0 -0
  157. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/cvxpy.py +0 -0
  158. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/dataclasses.py +0 -0
  159. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/datetime.py +0 -0
  160. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/enum.py +0 -0
  161. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/errors.py +0 -0
  162. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/eventkit.py +0 -0
  163. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/fastapi.py +0 -0
  164. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/fpdf2.py +0 -0
  165. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/functools.py +0 -0
  166. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/getpass.py +0 -0
  167. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/git.py +0 -0
  168. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/hashlib.py +0 -0
  169. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/http.py +0 -0
  170. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/hypothesis.py +0 -0
  171. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/ipython.py +0 -0
  172. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/iterables.py +0 -0
  173. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/jupyter.py +0 -0
  174. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/logging.py +0 -0
  175. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/loguru.py +0 -0
  176. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/luigi.py +0 -0
  177. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/math.py +0 -0
  178. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/memory_profiler.py +0 -0
  179. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/modules.py +0 -0
  180. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/more_itertools.py +0 -0
  181. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/numpy.py +0 -0
  182. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/operator.py +0 -0
  183. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/optuna.py +0 -0
  184. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/orjson.py +0 -0
  185. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/os.py +0 -0
  186. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/pathlib.py +0 -0
  187. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/period.py +0 -0
  188. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/pickle.py +0 -0
  189. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/platform.py +0 -0
  190. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/polars.py +0 -0
  191. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/pqdm.py +0 -0
  192. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/py.typed +0 -0
  193. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/pydantic.py +0 -0
  194. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/pyinstrument.py +0 -0
  195. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/pyrsistent.py +0 -0
  196. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/pytest.py +0 -0
  197. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/pytest_regressions.py +0 -0
  198. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/random.py +0 -0
  199. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/re.py +0 -0
  200. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/redis.py +0 -0
  201. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/reprlib.py +0 -0
  202. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/rich.py +0 -0
  203. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/scipy.py +0 -0
  204. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/shelve.py +0 -0
  205. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/slack_sdk.py +0 -0
  206. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/socket.py +0 -0
  207. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/sqlalchemy.py +0 -0
  208. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/sqlalchemy_polars.py +0 -0
  209. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/streamlit.py +0 -0
  210. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/sys.py +0 -0
  211. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/tempfile.py +0 -0
  212. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/tenacity.py +0 -0
  213. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/threading.py +0 -0
  214. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/timer.py +0 -0
  215. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/traceback.py +0 -0
  216. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/types.py +0 -0
  217. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/typing.py +0 -0
  218. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/tzdata.py +0 -0
  219. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/tzlocal.py +0 -0
  220. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/uuid.py +0 -0
  221. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/version.py +0 -0
  222. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/warnings.py +0 -0
  223. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/whenever.py +0 -0
  224. {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/zipfile.py +0 -0
  225. {dycw_utilities-0.108.1 → 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.1
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.1"
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.1"
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:
@@ -0,0 +1,242 @@
1
+ from __future__ import annotations
2
+
3
+ import datetime as dt
4
+ from collections.abc import Iterable
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+ from types import NoneType
8
+ from typing import Literal
9
+
10
+ from hypothesis import given
11
+ from hypothesis.strategies import booleans, dates, floats, integers, sampled_from, times
12
+ from pytest import raises
13
+
14
+ from tests.test_operator import TruthEnum
15
+ from utilities.functions import ensure_path
16
+ from utilities.hypothesis import (
17
+ local_datetimes,
18
+ paths,
19
+ text_ascii,
20
+ timedeltas_2w,
21
+ versions,
22
+ zoned_datetimes,
23
+ )
24
+ from utilities.math import is_equal
25
+ from utilities.parse import ParseTextError, parse_text
26
+ from utilities.sentinel import Sentinel, sentinel
27
+ from utilities.version import Version
28
+ from utilities.whenever import (
29
+ serialize_date,
30
+ serialize_datetime,
31
+ serialize_time,
32
+ serialize_timedelta,
33
+ )
34
+
35
+
36
+ class TestParseText:
37
+ @given(value=booleans())
38
+ def test_bool(self, *, value: bool) -> None:
39
+ text = str(value)
40
+ result = parse_text(bool, text)
41
+ assert result is value
42
+
43
+ @given(date=dates())
44
+ def test_date(self, *, date: dt.date) -> None:
45
+ text = serialize_date(date)
46
+ result = parse_text(dt.date, text)
47
+ assert result == date
48
+
49
+ @given(datetime=local_datetimes() | zoned_datetimes())
50
+ def test_datetime(self, *, datetime: dt.datetime) -> None:
51
+ text = serialize_datetime(datetime)
52
+ result = parse_text(dt.datetime, text)
53
+ assert result == datetime
54
+
55
+ @given(truth=sampled_from(TruthEnum))
56
+ def test_enum(self, *, truth: TruthEnum) -> None:
57
+ text = truth.name
58
+ result = parse_text(TruthEnum, text)
59
+ assert result is truth
60
+
61
+ @given(value=floats())
62
+ def test_float(self, *, value: float) -> None:
63
+ text = str(value)
64
+ result = parse_text(float, text)
65
+ assert is_equal(result, value)
66
+
67
+ @given(value=integers())
68
+ def test_int(self, *, value: int) -> None:
69
+ text = str(value)
70
+ result = parse_text(int, text)
71
+ assert result == value
72
+
73
+ @given(truth=sampled_from(["true", "false"]))
74
+ def test_literal(self, *, truth: Literal["true", "false"]) -> None:
75
+ result = parse_text(Literal["true", "false"], truth)
76
+ assert result == truth
77
+
78
+ def test_nullable_int_none(self) -> None:
79
+ text = str(None)
80
+ result = parse_text(int | None, text)
81
+ assert result is None
82
+
83
+ @given(value=integers())
84
+ def test_nullable_int_int(self, *, value: int) -> None:
85
+ text = str(value)
86
+ result = parse_text(int | None, text)
87
+ assert result == value
88
+
89
+ def test_none(self) -> None:
90
+ text = str(None)
91
+ result = parse_text(None, text)
92
+ assert result is None
93
+
94
+ def test_none_type(self) -> None:
95
+ text = str(None)
96
+ result = parse_text(NoneType, text)
97
+ assert result is None
98
+
99
+ @given(path=paths())
100
+ def test_path(self, *, path: Path) -> None:
101
+ text = str(path)
102
+ result = parse_text(Path, text)
103
+ assert result == path
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
+
112
+ def test_sentinel(self) -> None:
113
+ text = str(sentinel)
114
+ result = parse_text(Sentinel, text)
115
+ assert result is sentinel
116
+
117
+ @given(text=text_ascii())
118
+ def test_str(self, *, text: str) -> None:
119
+ result = parse_text(str, text)
120
+ assert result == text
121
+
122
+ @given(time=times())
123
+ def test_time(self, *, time: dt.time) -> None:
124
+ text = serialize_time(time)
125
+ result = parse_text(dt.time, text)
126
+ assert result == time
127
+
128
+ @given(timedelta=timedeltas_2w())
129
+ def test_timedelta(self, *, timedelta: dt.timedelta) -> None:
130
+ text = serialize_timedelta(timedelta)
131
+ result = parse_text(dt.timedelta, text)
132
+ assert result == timedelta
133
+
134
+ @given(version=versions())
135
+ def test_version(self, *, version: Version) -> None:
136
+ text = str(version)
137
+ result = parse_text(Version, text)
138
+ assert result == version
139
+
140
+ def test_error_bool(self) -> None:
141
+ with raises(
142
+ ParseTextError, match="Unable to parse <class 'bool'>; got 'invalid'"
143
+ ):
144
+ _ = parse_text(bool, "invalid")
145
+
146
+ def test_error_date(self) -> None:
147
+ with raises(
148
+ ParseTextError,
149
+ match=r"Unable to parse <class 'datetime\.date'>; got 'invalid'",
150
+ ):
151
+ _ = parse_text(dt.date, "invalid")
152
+
153
+ def test_error_datetime(self) -> None:
154
+ with raises(
155
+ ParseTextError,
156
+ match=r"Unable to parse <class 'datetime\.datetime'>; got 'invalid'",
157
+ ):
158
+ _ = parse_text(dt.datetime, "invalid")
159
+
160
+ def test_error_enum(self) -> None:
161
+ with raises(
162
+ ParseTextError, match="Unable to parse <enum 'TruthEnum'>; got 'invalid'"
163
+ ):
164
+ _ = parse_text(TruthEnum, "invalid")
165
+
166
+ def test_error_float(self) -> None:
167
+ with raises(
168
+ ParseTextError, match="Unable to parse <class 'float'>; got 'invalid'"
169
+ ):
170
+ _ = parse_text(float, "invalid")
171
+
172
+ def test_error_int(self) -> None:
173
+ with raises(
174
+ ParseTextError, match="Unable to parse <class 'int'>; got 'invalid'"
175
+ ):
176
+ _ = parse_text(int, "invalid")
177
+
178
+ def test_error_none(self) -> None:
179
+ with raises(ParseTextError, match="Unable to parse None; got 'invalid'"):
180
+ _ = parse_text(None, "invalid")
181
+
182
+ def test_error_none_type(self) -> None:
183
+ with raises(
184
+ ParseTextError, match="Unable to parse <class 'NoneType'>; got 'invalid'"
185
+ ):
186
+ _ = parse_text(NoneType, "invalid")
187
+
188
+ def test_error_nullable_int(self) -> None:
189
+ with raises(
190
+ ParseTextError, match=r"Unable to parse int \| None; got 'invalid'"
191
+ ):
192
+ _ = parse_text(int | None, "invalid")
193
+
194
+ def test_error_nullable_not_type(self) -> None:
195
+ with raises(
196
+ ParseTextError,
197
+ match=r"Unable to parse collections\.abc\.Iterable\[None\] \| None; got 'invalid'",
198
+ ):
199
+ _ = parse_text(Iterable[None] | None, "invalid")
200
+
201
+ def test_error_sentinel(self) -> None:
202
+ with raises(
203
+ ParseTextError,
204
+ match=r"Unable to parse <class 'utilities\.sentinel\.Sentinel'>; got 'invalid'",
205
+ ):
206
+ _ = parse_text(Sentinel, "invalid")
207
+
208
+ def test_error_time(self) -> None:
209
+ with raises(
210
+ ParseTextError,
211
+ match=r"Unable to parse <class 'datetime\.time'>; got 'invalid'",
212
+ ):
213
+ _ = parse_text(dt.time, "invalid")
214
+
215
+ def test_error_timedelta(self) -> None:
216
+ with raises(
217
+ ParseTextError,
218
+ match=r"Unable to parse <class 'datetime\.timedelta'>; got 'invalid'",
219
+ ):
220
+ _ = parse_text(dt.timedelta, "invalid")
221
+
222
+ def test_error_unknown_annotation(self) -> None:
223
+ with raises(ParseTextError, match=r"Unable to parse int \| str; got 'invalid'"):
224
+ _ = parse_text(int | str, "invalid")
225
+
226
+ def test_error_unknown_type(self) -> None:
227
+ @dataclass(kw_only=True)
228
+ class Example:
229
+ pass
230
+
231
+ with raises(
232
+ ParseTextError,
233
+ match=r"Unable to parse <class 'tests\.test_parse\.TestParseText\.test_error_unknown_type\.<locals>\.Example'>; got 'invalid'",
234
+ ):
235
+ _ = parse_text(Example, "invalid")
236
+
237
+ def test_error_version(self) -> None:
238
+ with raises(
239
+ ParseTextError,
240
+ match=r"Unable to parse <class 'utilities\.version\.Version'>; got 'invalid'",
241
+ ):
242
+ _ = parse_text(Version, "invalid")
@@ -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)
@@ -0,0 +1,45 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from hypothesis import given
6
+ from hypothesis.strategies import DataObject, data, sampled_from
7
+ from pytest import mark, param, raises
8
+
9
+ from utilities.sentinel import (
10
+ SENTINEL_REPR,
11
+ ParseSentinelError,
12
+ Sentinel,
13
+ parse_sentinel,
14
+ sentinel,
15
+ )
16
+
17
+ if TYPE_CHECKING:
18
+ from collections.abc import Callable
19
+
20
+
21
+ class TestParseSentinel:
22
+ @given(data=data())
23
+ def test_main(self, *, data: DataObject) -> None:
24
+ text = str(sentinel)
25
+ text_use = data.draw(sampled_from(["", text, text.lower(), text.upper()]))
26
+ result = parse_sentinel(text_use)
27
+ assert result is sentinel
28
+
29
+ def test_error(self) -> None:
30
+ with raises(
31
+ ParseSentinelError, match="Unable to parse sentinel value; got 'invalid'"
32
+ ):
33
+ _ = parse_sentinel("invalid")
34
+
35
+
36
+ class TestSentinel:
37
+ def test_isinstance(self) -> None:
38
+ assert isinstance(sentinel, Sentinel)
39
+
40
+ @mark.parametrize("method", [param(repr), param(str)])
41
+ def test_repr_and_str(self, method: Callable[..., str]) -> None:
42
+ assert method(sentinel) == SENTINEL_REPR
43
+
44
+ def test_singletone(self) -> None:
45
+ assert Sentinel() is sentinel
@@ -16,8 +16,10 @@ from utilities.hypothesis import text_ascii
16
16
  from utilities.sentinel import sentinel
17
17
  from utilities.text import (
18
18
  ParseBoolError,
19
+ ParseNoneError,
19
20
  join_strs,
20
21
  parse_bool,
22
+ parse_none,
21
23
  repr_encode,
22
24
  snake_case,
23
25
  split_str,
@@ -29,18 +31,33 @@ from utilities.text import (
29
31
  class TestParseBool:
30
32
  @given(data=data(), value=booleans())
31
33
  def test_main(self, *, data: DataObject, value: bool) -> None:
32
- text = data.draw(sampled_from([str(value), str(int(value))]))
33
- text = data.draw(sampled_from([text, text.lower(), text.upper()]))
34
- result = parse_bool(text)
34
+ text = str(value)
35
+ text_use = data.draw(
36
+ sampled_from([str(int(value)), text, text.lower(), text.upper()])
37
+ )
38
+ result = parse_bool(text_use)
35
39
  assert result is value
36
40
 
37
41
  def test_error(self) -> None:
38
42
  with raises(
39
- ParseBoolError, match="Unable to parse 'invalid' into a boolean value"
43
+ ParseBoolError, match="Unable to parse boolean value; got 'invalid'"
40
44
  ):
41
45
  _ = parse_bool("invalid")
42
46
 
43
47
 
48
+ class TestParseNone:
49
+ @given(data=data())
50
+ def test_main(self, *, data: DataObject) -> None:
51
+ text = str(None)
52
+ text_use = data.draw(sampled_from(["", text, text.lower(), text.upper()]))
53
+ result = parse_none(text_use)
54
+ assert result is None
55
+
56
+ def test_error(self) -> None:
57
+ with raises(ParseNoneError, match="Unable to parse null value; got 'invalid'"):
58
+ _ = parse_none("invalid")
59
+
60
+
44
61
  class TestReprEncode:
45
62
  @given(n=integers())
46
63
  def test_main(self, *, n: int) -> None:
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.108.1"
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",