dycw-utilities 0.150.8__tar.gz → 0.150.10__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 (216) hide show
  1. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/PKG-INFO +1 -1
  2. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/pyproject.toml +2 -2
  3. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_hypothesis.py +44 -0
  4. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_postgres.py +9 -53
  5. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_sqlalchemy.py +55 -1
  6. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_text.py +7 -0
  7. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/__init__.py +1 -1
  8. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/hypothesis.py +37 -0
  9. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/postgres.py +19 -72
  10. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/sqlalchemy.py +77 -1
  11. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/text.py +9 -1
  12. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/.gitignore +0 -0
  13. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/LICENSE +0 -0
  14. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/README.md +0 -0
  15. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/__init__.py +0 -0
  16. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/conftest.py +0 -0
  17. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/modules/__init__.py +0 -0
  18. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/modules/package_missing/__init__.py +0 -0
  19. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/modules/package_missing/module.py +0 -0
  20. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/modules/package_with/__init__.py +0 -0
  21. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/modules/package_with/outer_1.py +0 -0
  22. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/modules/package_with/outer_2.py +0 -0
  23. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/modules/package_with/subpackage/__init__.py +0 -0
  24. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/modules/package_with/subpackage/inner_1.py +0 -0
  25. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/modules/package_with/subpackage/inner_2.py +0 -0
  26. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/modules/package_with/subpackage/inner_3.py +0 -0
  27. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/modules/package_without/__init__.py +0 -0
  28. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/modules/package_without/module_1.py +0 -0
  29. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/modules/package_without/module_2.py +0 -0
  30. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/modules/standalone.py +0 -0
  31. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/modules/with_imports.py +0 -0
  32. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -0
  33. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -0
  34. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -0
  35. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -0
  36. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -0
  37. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -0
  38. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -0
  39. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -0
  40. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_altair.py +0 -0
  41. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_asyncio.py +0 -0
  42. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_atomicwrites.py +0 -0
  43. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_atools.py +0 -0
  44. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_cachetools.py +0 -0
  45. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_click.py +0 -0
  46. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_concurrent.py +0 -0
  47. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_contextlib.py +0 -0
  48. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_contextvars.py +0 -0
  49. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_cryptography.py +0 -0
  50. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_cvxpy.py +0 -0
  51. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_dataclasses.py +0 -0
  52. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_dnspython.py +0 -0
  53. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_enum.py +0 -0
  54. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_errors.py +0 -0
  55. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_eventkit.py +0 -0
  56. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_fastapi.py +0 -0
  57. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_fpdf2.py +0 -0
  58. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_functions.py +0 -0
  59. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_functools.py +0 -0
  60. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_getpass.py +0 -0
  61. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_gzip.py +0 -0
  62. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_hashlib.py +0 -0
  63. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_http.py +0 -0
  64. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_importlib.py +0 -0
  65. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_inflect.py +0 -0
  66. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_ipython.py +0 -0
  67. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_iterables.py +0 -0
  68. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_json.py +0 -0
  69. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_jupyter.py +0 -0
  70. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_libcst.py +0 -0
  71. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_lightweight_charts.py +0 -0
  72. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_logging.py +0 -0
  73. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_math.py +0 -0
  74. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_memory_profiler.py +0 -0
  75. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_modules.py +0 -0
  76. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_more_itertools.py +0 -0
  77. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_numpy.py +0 -0
  78. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_objects/__init__.py +0 -0
  79. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_objects/objects.py +0 -0
  80. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_operator.py +0 -0
  81. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_optuna.py +0 -0
  82. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_orjson.py +0 -0
  83. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_os.py +0 -0
  84. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_parse.py +0 -0
  85. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_pathlib.py +0 -0
  86. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_period.py +0 -0
  87. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_pickle.py +0 -0
  88. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_platform.py +0 -0
  89. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_polars.py +0 -0
  90. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_polars_ols.py +0 -0
  91. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_pottery.py +0 -0
  92. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_pqdm.py +0 -0
  93. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_psutil.py +0 -0
  94. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_pyinstrument.py +0 -0
  95. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_pytest.py +0 -0
  96. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_pytest_randomly.py +0 -0
  97. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_pytest_regressions.py +0 -0
  98. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_random.py +0 -0
  99. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_re.py +0 -0
  100. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_redis.py +0 -0
  101. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_reprlib.py +0 -0
  102. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_scipy.py +0 -0
  103. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_sentinel.py +0 -0
  104. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_shelve.py +0 -0
  105. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_slack_sdk.py +0 -0
  106. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_socket.py +0 -0
  107. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_sqlalchemy_polars.py +0 -0
  108. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_statsmodels.py +0 -0
  109. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_string.py +0 -0
  110. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_tempfile.py +0 -0
  111. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_threading.py +0 -0
  112. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_timer.py +0 -0
  113. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_traceback.py +0 -0
  114. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_typed_settings.py +0 -0
  115. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_types.py +0 -0
  116. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_typing.py +0 -0
  117. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_typing_funcs/__init__.py +0 -0
  118. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_typing_funcs/no_future.py +0 -0
  119. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_typing_funcs/with_future.py +0 -0
  120. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_tzdata.py +0 -0
  121. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_tzlocal.py +0 -0
  122. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_uuid.py +0 -0
  123. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_version.py +0 -0
  124. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_warnings.py +0 -0
  125. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_whenever.py +0 -0
  126. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_yield_access/__init__.py +0 -0
  127. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_yield_access/script.py +0 -0
  128. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_yield_access/script.sh +0 -0
  129. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_zipfile.py +0 -0
  130. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/tests/test_zoneinfo.py +0 -0
  131. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/altair.py +0 -0
  132. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/asyncio.py +0 -0
  133. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/atomicwrites.py +0 -0
  134. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/atools.py +0 -0
  135. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/cachetools.py +0 -0
  136. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/click.py +0 -0
  137. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/concurrent.py +0 -0
  138. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/contextlib.py +0 -0
  139. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/contextvars.py +0 -0
  140. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/cryptography.py +0 -0
  141. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/cvxpy.py +0 -0
  142. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/dataclasses.py +0 -0
  143. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/dnspython.py +0 -0
  144. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/enum.py +0 -0
  145. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/errors.py +0 -0
  146. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/eventkit.py +0 -0
  147. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/fastapi.py +0 -0
  148. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/fpdf2.py +0 -0
  149. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/functions.py +0 -0
  150. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/functools.py +0 -0
  151. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/getpass.py +0 -0
  152. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/gzip.py +0 -0
  153. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/hashlib.py +0 -0
  154. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/http.py +0 -0
  155. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/importlib.py +0 -0
  156. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/inflect.py +0 -0
  157. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/ipython.py +0 -0
  158. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/iterables.py +0 -0
  159. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/json.py +0 -0
  160. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/jupyter.py +0 -0
  161. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/libcst.py +0 -0
  162. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/lightweight_charts.py +0 -0
  163. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/logging.py +0 -0
  164. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/math.py +0 -0
  165. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/memory_profiler.py +0 -0
  166. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/modules.py +0 -0
  167. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/more_itertools.py +0 -0
  168. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/numpy.py +0 -0
  169. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/operator.py +0 -0
  170. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/optuna.py +0 -0
  171. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/orjson.py +0 -0
  172. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/os.py +0 -0
  173. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/parse.py +0 -0
  174. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/pathlib.py +0 -0
  175. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/period.py +0 -0
  176. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/pickle.py +0 -0
  177. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/platform.py +0 -0
  178. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/polars.py +0 -0
  179. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/polars_ols.py +0 -0
  180. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/pottery.py +0 -0
  181. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/pqdm.py +0 -0
  182. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/psutil.py +0 -0
  183. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/py.typed +0 -0
  184. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/pyinstrument.py +0 -0
  185. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/pytest.py +0 -0
  186. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/pytest_plugins/__init__.py +0 -0
  187. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/pytest_plugins/pytest_randomly.py +0 -0
  188. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/pytest_plugins/pytest_regressions.py +0 -0
  189. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/pytest_regressions.py +0 -0
  190. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/random.py +0 -0
  191. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/re.py +0 -0
  192. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/redis.py +0 -0
  193. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/reprlib.py +0 -0
  194. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/scipy.py +0 -0
  195. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/sentinel.py +0 -0
  196. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/shelve.py +0 -0
  197. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/slack_sdk.py +0 -0
  198. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/socket.py +0 -0
  199. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/sqlalchemy_polars.py +0 -0
  200. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/statsmodels.py +0 -0
  201. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/string.py +0 -0
  202. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/tempfile.py +0 -0
  203. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/threading.py +0 -0
  204. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/timer.py +0 -0
  205. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/traceback.py +0 -0
  206. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/typed_settings.py +0 -0
  207. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/types.py +0 -0
  208. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/typing.py +0 -0
  209. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/tzdata.py +0 -0
  210. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/tzlocal.py +0 -0
  211. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/uuid.py +0 -0
  212. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/version.py +0 -0
  213. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/warnings.py +0 -0
  214. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/whenever.py +0 -0
  215. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/zipfile.py +0 -0
  216. {dycw_utilities-0.150.8 → dycw_utilities-0.150.10}/src/utilities/zoneinfo.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.150.8
3
+ Version: 0.150.10
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -103,7 +103,7 @@ dependencies = [
103
103
  name = "dycw-utilities"
104
104
  readme = "README.md"
105
105
  requires-python = ">= 3.12"
106
- version = "0.150.8"
106
+ version = "0.150.10"
107
107
 
108
108
  [project.entry-points.pytest11]
109
109
  pytest-randomly = "utilities.pytest_plugins.pytest_randomly"
@@ -136,7 +136,7 @@ test = [
136
136
  # bump-my-version
137
137
  [tool.bumpversion]
138
138
  allow_dirty = true
139
- current_version = "0.150.8"
139
+ current_version = "0.150.10"
140
140
 
141
141
  [[tool.bumpversion.files]]
142
142
  filename = "src/utilities/__init__.py"
@@ -86,6 +86,7 @@ from utilities.hypothesis import (
86
86
  triples,
87
87
  uint32s,
88
88
  uint64s,
89
+ urls,
89
90
  versions,
90
91
  year_months,
91
92
  zoned_datetimes,
@@ -964,6 +965,49 @@ class TestUInt64s:
964
965
  assert max(min_value, MIN_UINT64) <= x <= min(max_value, MAX_UINT64)
965
966
 
966
967
 
968
+ class TestURLs:
969
+ @given(
970
+ data=data(),
971
+ all_=booleans(),
972
+ username=booleans(),
973
+ password=booleans(),
974
+ host=booleans(),
975
+ port=booleans(),
976
+ database=booleans(),
977
+ )
978
+ def test_main(
979
+ self,
980
+ *,
981
+ data: DataObject,
982
+ all_: bool,
983
+ username: bool,
984
+ password: bool,
985
+ host: bool,
986
+ port: bool,
987
+ database: bool,
988
+ ) -> None:
989
+ url = data.draw(
990
+ urls(
991
+ all_=all_,
992
+ username=username,
993
+ password=password,
994
+ host=host,
995
+ port=port,
996
+ database=database,
997
+ )
998
+ )
999
+ if all_ or username:
1000
+ assert url.username is not None
1001
+ if all_ or password:
1002
+ assert url.password is not None
1003
+ if all_ or host:
1004
+ assert url.host is not None
1005
+ if all_ or port:
1006
+ assert url.port is not None
1007
+ if all_ or database:
1008
+ assert url.database is not None
1009
+
1010
+
967
1011
  class TestVersions:
968
1012
  @given(data=data(), suffix=booleans())
969
1013
  def test_main(self, *, data: DataObject, suffix: bool) -> None:
@@ -7,14 +7,10 @@ from hypothesis.strategies import DrawFn, booleans, composite, lists, none, samp
7
7
  from pytest import raises
8
8
  from sqlalchemy import URL, Column, Integer, MetaData, Table
9
9
 
10
- from utilities.hypothesis import integers, temp_paths, text_ascii
10
+ from utilities.hypothesis import integers, temp_paths, text_ascii, urls
11
11
  from utilities.postgres import (
12
12
  _build_pg_dump,
13
13
  _build_pg_restore_or_psql,
14
- _extract_url,
15
- _ExtractURLDatabaseError,
16
- _ExtractURLHostError,
17
- _ExtractURLPortError,
18
14
  _path_pg_dump,
19
15
  _PGDumpFormat,
20
16
  _resolve_data_only_and_clean,
@@ -40,30 +36,15 @@ def tables(draw: DrawFn, /) -> list[Table | str]:
40
36
  return [draw(sampled_from([n, t])) for n, t in zip(names, tables, strict=True)]
41
37
 
42
38
 
43
- @composite
44
- def urls(draw: DrawFn, /) -> URL:
45
- username = draw(text_ascii(min_size=1) | none())
46
- password = draw(text_ascii(min_size=1) | none())
47
- host = draw(text_ascii(min_size=1))
48
- port = draw(integers(min_value=1))
49
- database = draw(text_ascii(min_size=1))
50
- return URL.create(
51
- drivername="postgres",
52
- username=username,
53
- password=password,
54
- host=host,
55
- port=port,
56
- database=database,
57
- )
58
-
59
-
60
39
  class TestPGDump:
61
- @given(url=urls(), path=temp_paths(), logger=text_ascii(min_size=1) | none())
40
+ @given(
41
+ url=urls(all_=True), path=temp_paths(), logger=text_ascii(min_size=1) | none()
42
+ )
62
43
  async def test_main(self, *, url: URL, path: Path, logger: str | None) -> None:
63
44
  _ = await pg_dump(url, path, dry_run=True, logger=logger)
64
45
 
65
46
  @given(
66
- url=urls(),
47
+ url=urls(all_=True),
67
48
  path=temp_paths(),
68
49
  format_=sampled_from(get_literal_elements(_PGDumpFormat)),
69
50
  jobs=integers(min_value=0) | none(),
@@ -137,15 +118,16 @@ class TestResolveDataOnlyAndClean:
137
118
 
138
119
 
139
120
  class TestRestore:
140
- @given(url=urls(), path=temp_paths(), logger=text_ascii(min_size=1) | none())
121
+ @given(
122
+ url=urls(all_=True), path=temp_paths(), logger=text_ascii(min_size=1) | none()
123
+ )
141
124
  async def test_main(self, *, url: URL, path: Path, logger: str | None) -> None:
142
125
  _ = await restore(url, path, dry_run=True, logger=logger)
143
126
 
144
127
  @given(
145
- url=urls(),
128
+ url=urls(all_=True),
146
129
  path=temp_paths(),
147
130
  psql=booleans(),
148
- database=text_ascii(min_size=1) | none(),
149
131
  create=booleans(),
150
132
  jobs=integers(min_value=0) | none(),
151
133
  schema=lists(text_ascii(min_size=1)) | none(),
@@ -160,7 +142,6 @@ class TestRestore:
160
142
  url: URL,
161
143
  path: Path,
162
144
  psql: bool,
163
- database: str | None,
164
145
  create: bool,
165
146
  jobs: int | None,
166
147
  schema: list[str] | None,
@@ -173,7 +154,6 @@ class TestRestore:
173
154
  url,
174
155
  path,
175
156
  psql=psql,
176
- database=database,
177
157
  create=create,
178
158
  jobs=jobs,
179
159
  schema=schema,
@@ -182,27 +162,3 @@ class TestRestore:
182
162
  role=role,
183
163
  docker=docker,
184
164
  )
185
-
186
-
187
- class TestExtractURL:
188
- def test_database(self) -> None:
189
- url = URL.create("postgres")
190
- with raises(
191
- _ExtractURLDatabaseError,
192
- match="Expected URL to contain a 'database'; got .*",
193
- ):
194
- _ = _extract_url(url)
195
-
196
- def test_host(self) -> None:
197
- url = URL.create("postgres", database="database")
198
- with raises(
199
- _ExtractURLHostError, match="Expected URL to contain a 'host'; got .*"
200
- ):
201
- _ = _extract_url(url)
202
-
203
- def test_port(self) -> None:
204
- url = URL.create("postgres", database="database", host="host")
205
- with raises(
206
- _ExtractURLPortError, match="Expected URL to contain a 'port'; got .*"
207
- ):
208
- _ = _extract_url(url)
@@ -9,6 +9,7 @@ from hypothesis import HealthCheck, Phase, assume, given, settings
9
9
  from hypothesis.strategies import SearchStrategy, booleans, lists, none, sets, tuples
10
10
  from pytest import mark, param, raises
11
11
  from sqlalchemy import (
12
+ URL,
12
13
  Boolean,
13
14
  Column,
14
15
  Engine,
@@ -28,7 +29,7 @@ from sqlalchemy.orm import (
28
29
  relationship,
29
30
  )
30
31
 
31
- from utilities.hypothesis import int32s, pairs
32
+ from utilities.hypothesis import int32s, pairs, urls
32
33
  from utilities.iterables import one
33
34
  from utilities.modules import is_installed
34
35
  from utilities.sqlalchemy import (
@@ -39,6 +40,11 @@ from utilities.sqlalchemy import (
39
40
  TablenameMixin,
40
41
  TableOrORMInstOrClass,
41
42
  UpsertItemsError,
43
+ _ExtractURLDatabaseError,
44
+ _ExtractURLHostError,
45
+ _ExtractURLPasswordError,
46
+ _ExtractURLPortError,
47
+ _ExtractURLUsernameError,
42
48
  _get_dialect,
43
49
  _get_dialect_max_params,
44
50
  _InsertItem,
@@ -69,6 +75,7 @@ from utilities.sqlalchemy import (
69
75
  ensure_tables_dropped,
70
76
  enum_name,
71
77
  enum_values,
78
+ extract_url,
72
79
  get_chunk_size,
73
80
  get_column_names,
74
81
  get_columns,
@@ -313,6 +320,53 @@ class TestEnumValues:
313
320
  assert result == expected
314
321
 
315
322
 
323
+ class TestExtractURL:
324
+ @given(url=urls(all_=True))
325
+ def test_main(self, *, url: URL) -> None:
326
+ extracted = extract_url(url)
327
+ assert extracted.username == url.username
328
+ assert extracted.password == url.password
329
+ assert extracted.host == url.host
330
+ assert extracted.port == url.port
331
+ assert extracted.database == url.database
332
+
333
+ @given(url=urls(username=False))
334
+ def test_username(self, *, url: URL) -> None:
335
+ with raises(
336
+ _ExtractURLUsernameError,
337
+ match="Expected URL to contain a user name; got .*",
338
+ ):
339
+ _ = extract_url(url)
340
+
341
+ @given(url=urls(username=True, password=False))
342
+ def test_password(self, *, url: URL) -> None:
343
+ with raises(
344
+ _ExtractURLPasswordError, match="Expected URL to contain a password; got .*"
345
+ ):
346
+ _ = extract_url(url)
347
+
348
+ @given(url=urls(username=True, password=True, host=False))
349
+ def test_host(self, *, url: URL) -> None:
350
+ with raises(
351
+ _ExtractURLHostError, match="Expected URL to contain a host; got .*"
352
+ ):
353
+ _ = extract_url(url)
354
+
355
+ @given(url=urls(username=True, password=True, host=True, port=False))
356
+ def test_port(self, *, url: URL) -> None:
357
+ with raises(
358
+ _ExtractURLPortError, match="Expected URL to contain a port; got .*"
359
+ ):
360
+ _ = extract_url(url)
361
+
362
+ @given(url=urls(username=True, password=True, host=True, port=True, database=False))
363
+ def test_database(self, *, url: URL) -> None:
364
+ with raises(
365
+ _ExtractURLDatabaseError, match="Expected URL to contain a database; got .*"
366
+ ):
367
+ _ = extract_url(url)
368
+
369
+
316
370
  class TestGetChunkSize:
317
371
  @mark.parametrize(
318
372
  ("num_cols", "chunk_size_frac", "expected"),
@@ -112,6 +112,13 @@ class TestSecretStr:
112
112
  assert repr(s) == secret_str._REPR
113
113
  assert str(s) == secret_str._REPR
114
114
 
115
+ def test_open(self) -> None:
116
+ s = secret_str("text")
117
+ assert isinstance(s.str, str)
118
+ assert not isinstance(s.str, secret_str)
119
+ assert repr(s.str) == repr("text")
120
+ assert str(s.str) == "text"
121
+
115
122
 
116
123
  class TestSnakeCase:
117
124
  @given(
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.150.8"
3
+ __version__ = "0.150.10"
@@ -98,6 +98,7 @@ if TYPE_CHECKING:
98
98
  from hypothesis.database import ExampleDatabase
99
99
  from libcst import Import, ImportFrom
100
100
  from numpy.random import RandomState
101
+ from sqlalchemy import URL
101
102
 
102
103
  from utilities.numpy import NDArrayB, NDArrayF, NDArrayI, NDArrayO
103
104
  from utilities.types import Number, TimeZoneLike
@@ -1258,6 +1259,41 @@ def uint64s(
1258
1259
  ##
1259
1260
 
1260
1261
 
1262
+ @composite
1263
+ def urls(
1264
+ draw: DrawFn,
1265
+ /,
1266
+ *,
1267
+ all_: MaybeSearchStrategy[bool] = False,
1268
+ username: MaybeSearchStrategy[bool] = False,
1269
+ password: MaybeSearchStrategy[bool] = False,
1270
+ host: MaybeSearchStrategy[bool] = False,
1271
+ port: MaybeSearchStrategy[bool] = False,
1272
+ database: MaybeSearchStrategy[bool] = False,
1273
+ ) -> URL:
1274
+ from sqlalchemy import URL
1275
+
1276
+ have_all, have_username, have_password, have_host, have_port, have_database = [
1277
+ draw2(draw, b) for b in [all_, username, password, host, port, database]
1278
+ ]
1279
+ username_use = draw(text_ascii(min_size=1)) if have_all or have_username else None
1280
+ password_use = draw(text_ascii(min_size=1)) if have_all or have_password else None
1281
+ host_use = draw(text_ascii(min_size=1)) if have_all or have_host else None
1282
+ port_use = draw(integers(min_value=1)) if have_all or have_port else None
1283
+ database_use = draw(text_ascii(min_size=1)) if have_all or have_database else None
1284
+ return URL.create(
1285
+ drivername="sqlite",
1286
+ username=username_use,
1287
+ password=password_use,
1288
+ host=host_use,
1289
+ port=port_use,
1290
+ database=database_use,
1291
+ )
1292
+
1293
+
1294
+ ##
1295
+
1296
+
1261
1297
  @composite
1262
1298
  def versions(draw: DrawFn, /, *, suffix: MaybeSearchStrategy[bool] = False) -> Version:
1263
1299
  """Strategy for generating versions."""
@@ -1392,6 +1428,7 @@ __all__ = [
1392
1428
  "triples",
1393
1429
  "uint32s",
1394
1430
  "uint64s",
1431
+ "urls",
1395
1432
  "versions",
1396
1433
  "year_months",
1397
1434
  "zoned_datetimes",
@@ -13,7 +13,7 @@ from utilities.iterables import always_iterable
13
13
  from utilities.logging import get_logger
14
14
  from utilities.os import temp_environ
15
15
  from utilities.pathlib import ensure_suffix
16
- from utilities.sqlalchemy import get_table_name
16
+ from utilities.sqlalchemy import extract_url, get_table_name
17
17
  from utilities.timer import Timer
18
18
  from utilities.types import PathLike
19
19
 
@@ -124,12 +124,11 @@ def _build_pg_dump(
124
124
  role: str | None = None,
125
125
  docker: str | None = None,
126
126
  ) -> str:
127
- database, host, port = _extract_url(url)
127
+ extracted = extract_url(url)
128
128
  path = _path_pg_dump(path, format_=format_)
129
129
  parts: list[str] = [
130
130
  "pg_dump",
131
131
  # general options
132
- f"--dbname={database}",
133
132
  f"--file={str(path)!r}",
134
133
  f"--format={format_}",
135
134
  "--verbose",
@@ -139,8 +138,10 @@ def _build_pg_dump(
139
138
  "--no-owner",
140
139
  "--no-privileges",
141
140
  # connection options
142
- f"--host={host}",
143
- f"--port={port}",
141
+ f"--dbname={extracted.database}",
142
+ f"--host={extracted.host}",
143
+ f"--port={extracted.port}",
144
+ f"--username={extracted.username}",
144
145
  "--no-password",
145
146
  ]
146
147
  if (format_ == "directory") and (jobs is not None):
@@ -167,8 +168,6 @@ def _build_pg_dump(
167
168
  parts.append("--inserts")
168
169
  if on_conflict_do_nothing:
169
170
  parts.append("--on-conflict-do-nothing")
170
- if url.username is not None:
171
- parts.append(f"--username={url.username}")
172
171
  if role is not None:
173
172
  parts.append(f"--role={role}")
174
173
  if docker is not None:
@@ -203,7 +202,6 @@ async def restore(
203
202
  /,
204
203
  *,
205
204
  psql: bool = False,
206
- database: str | None = None,
207
205
  data_only: bool = False,
208
206
  clean: bool = False,
209
207
  create: bool = False,
@@ -221,7 +219,6 @@ async def restore(
221
219
  url,
222
220
  path,
223
221
  psql=psql,
224
- database=database,
225
222
  data_only=data_only,
226
223
  clean=clean,
227
224
  create=create,
@@ -270,7 +267,6 @@ def _build_pg_restore_or_psql(
270
267
  /,
271
268
  *,
272
269
  psql: bool = False,
273
- database: str | None = None,
274
270
  data_only: bool = False,
275
271
  clean: bool = False,
276
272
  create: bool = False,
@@ -283,11 +279,10 @@ def _build_pg_restore_or_psql(
283
279
  ) -> str:
284
280
  path = Path(path)
285
281
  if (path.suffix == ".sql") or psql:
286
- return _build_psql(url, path, database=database, docker=docker)
282
+ return _build_psql(url, path, docker=docker)
287
283
  return _build_pg_restore(
288
284
  url,
289
285
  path,
290
- database=database,
291
286
  data_only=data_only,
292
287
  clean=clean,
293
288
  create=create,
@@ -305,7 +300,6 @@ def _build_pg_restore(
305
300
  path: PathLike,
306
301
  /,
307
302
  *,
308
- database: str | None = None,
309
303
  data_only: bool = False,
310
304
  clean: bool = False,
311
305
  create: bool = False,
@@ -317,12 +311,10 @@ def _build_pg_restore(
317
311
  docker: str | None = None,
318
312
  ) -> str:
319
313
  """Run `pg_restore`."""
320
- url_database, host, port = _extract_url(url)
321
- database_use = url_database if database is None else database
314
+ extracted = extract_url(url)
322
315
  parts: list[str] = [
323
316
  "pg_restore",
324
317
  # general options
325
- f"--dbname={database_use}",
326
318
  "--verbose",
327
319
  # restore options
328
320
  *_resolve_data_only_and_clean(data_only=data_only, clean=clean),
@@ -330,8 +322,10 @@ def _build_pg_restore(
330
322
  "--no-owner",
331
323
  "--no-privileges",
332
324
  # connection options
333
- f"--host={host}",
334
- f"--port={port}",
325
+ f"--host={extracted.host}",
326
+ f"--port={extracted.port}",
327
+ f"--username={extracted.username}",
328
+ f"--dbname={extracted.database}",
335
329
  "--no-password",
336
330
  ]
337
331
  if create:
@@ -344,8 +338,6 @@ def _build_pg_restore(
344
338
  parts.extend([f"--exclude-schema={s}" for s in always_iterable(schemas_exc)])
345
339
  if tables is not None:
346
340
  parts.extend([f"--table={_get_table_name(t)}" for t in always_iterable(tables)])
347
- if url.username is not None:
348
- parts.append(f"--username={url.username}")
349
341
  if role is not None:
350
342
  parts.append(f"--role={role}")
351
343
  if docker is not None:
@@ -354,29 +346,20 @@ def _build_pg_restore(
354
346
  return " ".join(parts)
355
347
 
356
348
 
357
- def _build_psql(
358
- url: URL,
359
- path: PathLike,
360
- /,
361
- *,
362
- database: str | None = None,
363
- docker: str | None = None,
364
- ) -> str:
349
+ def _build_psql(url: URL, path: PathLike, /, *, docker: str | None = None) -> str:
365
350
  """Run `psql`."""
366
- url_database, host, port = _extract_url(url)
367
- database_use = url_database if database is None else database
351
+ extracted = extract_url(url)
368
352
  parts: list[str] = [
369
353
  "psql",
370
354
  # general options
371
- f"--dbname={database_use}",
355
+ f"--dbname={extracted.database}",
372
356
  f"--file={str(path)!r}",
373
357
  # connection options
374
- f"--host={host}",
375
- f"--port={port}",
358
+ f"--host={extracted.host}",
359
+ f"--port={extracted.port}",
360
+ f"--username={extracted.username}",
376
361
  "--no-password",
377
362
  ]
378
- if url.username is not None:
379
- parts.append(f"--username={url.username}")
380
363
  if docker is not None:
381
364
  parts = _wrap_docker(parts, docker)
382
365
  return " ".join(parts)
@@ -385,42 +368,6 @@ def _build_psql(
385
368
  ##
386
369
 
387
370
 
388
- def _extract_url(url: URL, /) -> tuple[str, str, int]:
389
- if url.database is None:
390
- raise _ExtractURLDatabaseError(url=url)
391
- if url.host is None:
392
- raise _ExtractURLHostError(url=url)
393
- if url.port is None:
394
- raise _ExtractURLPortError(url=url)
395
- return url.database, url.host, url.port
396
-
397
-
398
- @dataclass(kw_only=True, slots=True)
399
- class ExtractURLError(Exception):
400
- url: URL
401
-
402
-
403
- @dataclass(kw_only=True, slots=True)
404
- class _ExtractURLDatabaseError(ExtractURLError):
405
- @override
406
- def __str__(self) -> str:
407
- return f"Expected URL to contain a 'database'; got {self.url}"
408
-
409
-
410
- @dataclass(kw_only=True, slots=True)
411
- class _ExtractURLHostError(ExtractURLError):
412
- @override
413
- def __str__(self) -> str:
414
- return f"Expected URL to contain a 'host'; got {self.url}"
415
-
416
-
417
- @dataclass(kw_only=True, slots=True)
418
- class _ExtractURLPortError(ExtractURLError):
419
- @override
420
- def __str__(self) -> str:
421
- return f"Expected URL to contain a 'port'; got {self.url}"
422
-
423
-
424
371
  def _get_table_name(obj: TableOrORMInstOrClass | str, /) -> str:
425
372
  match obj:
426
373
  case Table() | DeclarativeBase() | type() as table_or_orm:
@@ -458,4 +405,4 @@ def _wrap_docker(parts: list[str], container: str, /) -> list[str]:
458
405
  return ["docker", "exec", "-it", container, *parts]
459
406
 
460
407
 
461
- __all__ = ["ExtractURLError", "pg_dump", "restore"]
408
+ __all__ = ["pg_dump", "restore"]
@@ -96,7 +96,7 @@ from utilities.iterables import (
96
96
  one,
97
97
  )
98
98
  from utilities.reprlib import get_repr
99
- from utilities.text import snake_case
99
+ from utilities.text import secret_str, snake_case
100
100
  from utilities.types import MaybeIterable, MaybeType, StrMapping, TupleOrStrMapping
101
101
 
102
102
  if TYPE_CHECKING:
@@ -378,6 +378,79 @@ def enum_values(enum: type[StrEnum], /) -> list[str]:
378
378
  ##
379
379
 
380
380
 
381
+ @dataclass(kw_only=True, slots=True)
382
+ class ExtractURLOutput:
383
+ username: str
384
+ password: secret_str
385
+ host: str
386
+ port: int
387
+ database: str
388
+
389
+
390
+ def extract_url(url: URL, /) -> ExtractURLOutput:
391
+ """Extract the database, host & port from a URL."""
392
+ if url.username is None:
393
+ raise _ExtractURLUsernameError(url=url)
394
+ if url.password is None:
395
+ raise _ExtractURLPasswordError(url=url)
396
+ if url.host is None:
397
+ raise _ExtractURLHostError(url=url)
398
+ if url.port is None:
399
+ raise _ExtractURLPortError(url=url)
400
+ if url.database is None:
401
+ raise _ExtractURLDatabaseError(url=url)
402
+ return ExtractURLOutput(
403
+ username=url.username,
404
+ password=secret_str(url.password),
405
+ host=url.host,
406
+ port=url.port,
407
+ database=url.database,
408
+ )
409
+
410
+
411
+ @dataclass(kw_only=True, slots=True)
412
+ class ExtractURLError(Exception):
413
+ url: URL
414
+
415
+
416
+ @dataclass(kw_only=True, slots=True)
417
+ class _ExtractURLUsernameError(ExtractURLError):
418
+ @override
419
+ def __str__(self) -> str:
420
+ return f"Expected URL to contain a user name; got {self.url}"
421
+
422
+
423
+ @dataclass(kw_only=True, slots=True)
424
+ class _ExtractURLPasswordError(ExtractURLError):
425
+ @override
426
+ def __str__(self) -> str:
427
+ return f"Expected URL to contain a password; got {self.url}"
428
+
429
+
430
+ @dataclass(kw_only=True, slots=True)
431
+ class _ExtractURLHostError(ExtractURLError):
432
+ @override
433
+ def __str__(self) -> str:
434
+ return f"Expected URL to contain a host; got {self.url}"
435
+
436
+
437
+ @dataclass(kw_only=True, slots=True)
438
+ class _ExtractURLPortError(ExtractURLError):
439
+ @override
440
+ def __str__(self) -> str:
441
+ return f"Expected URL to contain a port; got {self.url}"
442
+
443
+
444
+ @dataclass(kw_only=True, slots=True)
445
+ class _ExtractURLDatabaseError(ExtractURLError):
446
+ @override
447
+ def __str__(self) -> str:
448
+ return f"Expected URL to contain a database; got {self.url}"
449
+
450
+
451
+ ##
452
+
453
+
381
454
  def get_chunk_size(
382
455
  dialect_or_engine_or_conn: DialectOrEngineOrConnectionOrAsync,
383
456
  table_or_orm_or_num_cols: TableOrORMInstOrClass | Sized | int,
@@ -1180,6 +1253,8 @@ __all__ = [
1180
1253
  "CheckEngineError",
1181
1254
  "DialectOrEngineOrConnectionOrAsync",
1182
1255
  "EngineOrConnectionOrAsync",
1256
+ "ExtractURLError",
1257
+ "ExtractURLOutput",
1183
1258
  "GetTableError",
1184
1259
  "InsertItemsError",
1185
1260
  "TablenameMixin",
@@ -1193,6 +1268,7 @@ __all__ = [
1193
1268
  "ensure_tables_dropped",
1194
1269
  "enum_name",
1195
1270
  "enum_values",
1271
+ "extract_url",
1196
1272
  "get_chunk_size",
1197
1273
  "get_column_names",
1198
1274
  "get_columns",