dycw-utilities 0.110.3__tar.gz → 0.110.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/PKG-INFO +1 -1
  2. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/pyproject.toml +2 -2
  3. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_dataclasses.py +97 -64
  4. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_orjson.py +1 -0
  5. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_parse.py +46 -26
  6. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_text.py +1 -0
  7. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_typing_funcs/with_future.py +16 -0
  8. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/__init__.py +1 -1
  9. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/dataclasses.py +39 -10
  10. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/parse.py +40 -3
  11. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/text.py +4 -1
  12. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/types.py +2 -0
  13. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/.gitignore +0 -0
  14. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/LICENSE +0 -0
  15. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/README.md +0 -0
  16. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/__init__.py +0 -0
  17. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/conftest.py +0 -0
  18. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/modules/__init__.py +0 -0
  19. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/modules/package_missing/__init__.py +0 -0
  20. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/modules/package_missing/module.py +0 -0
  21. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/modules/package_with/__init__.py +0 -0
  22. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/modules/package_with/outer_1.py +0 -0
  23. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/modules/package_with/outer_2.py +0 -0
  24. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/modules/package_with/subpackage/__init__.py +0 -0
  25. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/modules/package_with/subpackage/inner_1.py +0 -0
  26. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/modules/package_with/subpackage/inner_2.py +0 -0
  27. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/modules/package_with/subpackage/inner_3.py +0 -0
  28. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/modules/package_without/__init__.py +0 -0
  29. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/modules/package_without/module_1.py +0 -0
  30. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/modules/package_without/module_2.py +0 -0
  31. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/modules/standalone.py +0 -0
  32. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/modules/with_imports.py +0 -0
  33. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -0
  34. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -0
  35. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -0
  36. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -0
  37. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -0
  38. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -0
  39. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -0
  40. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -0
  41. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/scripts/__init__.py +0 -0
  42. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/scripts/test_async_service/__init__.py +0 -0
  43. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/scripts/test_async_service/__main__.py +0 -0
  44. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/scripts/test_async_service/run.sh +0 -0
  45. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/scripts/test_queue_processor/__init__.py +0 -0
  46. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/scripts/test_queue_processor/__main__.py +0 -0
  47. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/scripts/test_queue_processor/run.sh +0 -0
  48. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_altair.py +0 -0
  49. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_astor.py +0 -0
  50. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_asyncio.py +0 -0
  51. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_atomicwrites.py +0 -0
  52. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_atools.py +0 -0
  53. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_cachetools.py +0 -0
  54. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_click.py +0 -0
  55. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_concurrent.py +0 -0
  56. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_contextlib.py +0 -0
  57. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_contextvars.py +0 -0
  58. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_cryptography.py +0 -0
  59. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_cvxpy.py +0 -0
  60. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_datetime.py +0 -0
  61. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_enum.py +0 -0
  62. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_errors.py +0 -0
  63. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_eventkit.py +0 -0
  64. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_fastapi.py +0 -0
  65. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_fpdf2.py +0 -0
  66. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_functions.py +0 -0
  67. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_functools.py +0 -0
  68. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_getpass.py +0 -0
  69. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_git.py +0 -0
  70. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_hashlib.py +0 -0
  71. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_http.py +0 -0
  72. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_hypothesis.py +0 -0
  73. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_ipython.py +0 -0
  74. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_iterables.py +0 -0
  75. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_jupyter.py +0 -0
  76. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_lightweight_charts.py +0 -0
  77. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_logging.py +0 -0
  78. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_loguru.py +0 -0
  79. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_luigi.py +0 -0
  80. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_math.py +0 -0
  81. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_memory_profiler.py +0 -0
  82. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_modules.py +0 -0
  83. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_more_itertools.py +0 -0
  84. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_numpy.py +0 -0
  85. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_operator.py +0 -0
  86. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_optuna.py +0 -0
  87. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_os.py +0 -0
  88. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_pathlib.py +0 -0
  89. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_period.py +0 -0
  90. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_pickle.py +0 -0
  91. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_platform.py +0 -0
  92. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_polars.py +0 -0
  93. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_polars_ols.py +0 -0
  94. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_pqdm.py +0 -0
  95. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_pydantic.py +0 -0
  96. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_pyinstrument.py +0 -0
  97. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_pyrsistent.py +0 -0
  98. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_pytest.py +0 -0
  99. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_pytest_regressions.py +0 -0
  100. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_python_dotenv.py +0 -0
  101. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_random.py +0 -0
  102. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_re.py +0 -0
  103. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_redis.py +0 -0
  104. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_reprlib.py +0 -0
  105. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_rich.py +0 -0
  106. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_scipy.py +0 -0
  107. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_sentinel.py +0 -0
  108. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_shelve.py +0 -0
  109. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_slack_sdk.py +0 -0
  110. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_socket.py +0 -0
  111. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_sqlalchemy.py +0 -0
  112. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_sqlalchemy_polars.py +0 -0
  113. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_statsmodel.py +0 -0
  114. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_streamlit.py +0 -0
  115. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_sys.py +0 -0
  116. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_tempfile.py +0 -0
  117. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_tenacity.py +0 -0
  118. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_threading.py +0 -0
  119. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_timer.py +0 -0
  120. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_traceback.py +0 -0
  121. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_traceback_funcs/__init__.py +0 -0
  122. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_traceback_funcs/chain.py +0 -0
  123. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_traceback_funcs/decorated_async.py +0 -0
  124. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_traceback_funcs/decorated_sync.py +0 -0
  125. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_traceback_funcs/error_bind.py +0 -0
  126. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_traceback_funcs/many.py +0 -0
  127. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_traceback_funcs/one.py +0 -0
  128. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_traceback_funcs/recursive.py +0 -0
  129. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_traceback_funcs/task_group_one.py +0 -0
  130. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_traceback_funcs/task_group_two.py +0 -0
  131. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_traceback_funcs/two.py +0 -0
  132. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_traceback_funcs/untraced.py +0 -0
  133. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_types.py +0 -0
  134. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_typing.py +0 -0
  135. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_typing_funcs/__init__.py +0 -0
  136. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_typing_funcs/no_future.py +0 -0
  137. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_tzdata.py +0 -0
  138. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_tzlocal.py +0 -0
  139. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_uuid.py +0 -0
  140. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_version.py +0 -0
  141. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_warnings.py +0 -0
  142. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_whenever.py +0 -0
  143. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_zipfile.py +0 -0
  144. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/tests/test_zoneinfo.py +0 -0
  145. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/altair.py +0 -0
  146. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/astor.py +0 -0
  147. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/asyncio.py +0 -0
  148. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/atomicwrites.py +0 -0
  149. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/atools.py +0 -0
  150. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/cachetools.py +0 -0
  151. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/click.py +0 -0
  152. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/concurrent.py +0 -0
  153. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/contextlib.py +0 -0
  154. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/contextvars.py +0 -0
  155. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/cryptography.py +0 -0
  156. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/cvxpy.py +0 -0
  157. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/datetime.py +0 -0
  158. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/enum.py +0 -0
  159. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/errors.py +0 -0
  160. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/eventkit.py +0 -0
  161. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/fastapi.py +0 -0
  162. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/fpdf2.py +0 -0
  163. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/functions.py +0 -0
  164. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/functools.py +0 -0
  165. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/getpass.py +0 -0
  166. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/git.py +0 -0
  167. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/hashlib.py +0 -0
  168. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/http.py +0 -0
  169. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/hypothesis.py +0 -0
  170. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/ipython.py +0 -0
  171. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/iterables.py +0 -0
  172. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/jupyter.py +0 -0
  173. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/lightweight_charts.py +0 -0
  174. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/logging.py +0 -0
  175. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/loguru.py +0 -0
  176. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/luigi.py +0 -0
  177. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/math.py +0 -0
  178. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/memory_profiler.py +0 -0
  179. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/modules.py +0 -0
  180. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/more_itertools.py +0 -0
  181. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/numpy.py +0 -0
  182. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/operator.py +0 -0
  183. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/optuna.py +0 -0
  184. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/orjson.py +0 -0
  185. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/os.py +0 -0
  186. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/pathlib.py +0 -0
  187. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/period.py +0 -0
  188. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/pickle.py +0 -0
  189. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/platform.py +0 -0
  190. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/polars.py +0 -0
  191. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/polars_ols.py +0 -0
  192. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/pqdm.py +0 -0
  193. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/py.typed +0 -0
  194. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/pydantic.py +0 -0
  195. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/pyinstrument.py +0 -0
  196. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/pyrsistent.py +0 -0
  197. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/pytest.py +0 -0
  198. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/pytest_regressions.py +0 -0
  199. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/python_dotenv.py +0 -0
  200. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/random.py +0 -0
  201. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/re.py +0 -0
  202. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/redis.py +0 -0
  203. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/reprlib.py +0 -0
  204. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/rich.py +0 -0
  205. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/scipy.py +0 -0
  206. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/sentinel.py +0 -0
  207. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/shelve.py +0 -0
  208. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/slack_sdk.py +0 -0
  209. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/socket.py +0 -0
  210. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/sqlalchemy.py +0 -0
  211. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/sqlalchemy_polars.py +0 -0
  212. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/statsmodels.py +0 -0
  213. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/streamlit.py +0 -0
  214. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/sys.py +0 -0
  215. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/tempfile.py +0 -0
  216. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/tenacity.py +0 -0
  217. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/threading.py +0 -0
  218. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/timer.py +0 -0
  219. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/traceback.py +0 -0
  220. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/typing.py +0 -0
  221. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/tzdata.py +0 -0
  222. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/tzlocal.py +0 -0
  223. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/uuid.py +0 -0
  224. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/version.py +0 -0
  225. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/warnings.py +0 -0
  226. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/whenever.py +0 -0
  227. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/zipfile.py +0 -0
  228. {dycw_utilities-0.110.3 → dycw_utilities-0.110.4}/src/utilities/zoneinfo.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.110.3
3
+ Version: 0.110.4
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -94,7 +94,7 @@ dependencies = [
94
94
  name = "dycw-utilities"
95
95
  readme = "README.md"
96
96
  requires-python = ">= 3.12"
97
- version = "0.110.3"
97
+ version = "0.110.4"
98
98
 
99
99
  [project.optional-dependencies]
100
100
  test = [
@@ -337,7 +337,7 @@ zzz-test-zoneinfo = [
337
337
  # bump-my-version
338
338
  [tool.bumpversion]
339
339
  allow_dirty = true
340
- current_version = "0.110.3"
340
+ current_version = "0.110.4"
341
341
 
342
342
  [[tool.bumpversion.files]]
343
343
  filename = "src/utilities/__init__.py"
@@ -38,6 +38,7 @@ from utilities.dataclasses import (
38
38
  OneFieldNonUniqueError,
39
39
  StrMappingToFieldMappingError,
40
40
  YieldFieldsError,
41
+ _parse_dataclass_split_key_value_pairs,
41
42
  _ParseDataClassParseValueError,
42
43
  _ParseDataClassSplitKeyValuePairsDuplicateKeysError,
43
44
  _ParseDataClassSplitKeyValuePairsSplitError,
@@ -364,6 +365,14 @@ class TestOneField:
364
365
  )
365
366
 
366
367
 
368
+ class TestParseDataClassSplitKeyValuePairs:
369
+ @given(text=sampled_from(["a=1,b=22,c=333", "{a=1,b=22,c=333}"]))
370
+ def test_main(self, *, text: str) -> None:
371
+ result = _parse_dataclass_split_key_value_pairs(text, DataClassFutureInt)
372
+ expected = {"a": "1", "b": "22", "c": "333"}
373
+ assert result == expected
374
+
375
+
367
376
  class TestReplaceNonSentinel:
368
377
  def test_main(self) -> None:
369
378
  obj = DataClassFutureIntDefault()
@@ -382,6 +391,94 @@ class TestReplaceNonSentinel:
382
391
  assert obj.int_ == 1
383
392
 
384
393
 
394
+ class TestSerializeAndParseDataClass:
395
+ @given(int_=integers())
396
+ def test_main_future_int(self, *, int_: int) -> None:
397
+ obj = DataClassFutureInt(int_=int_)
398
+ serialized = serialize_dataclass(obj)
399
+ result = parse_dataclass(serialized, DataClassFutureInt)
400
+ assert result == obj
401
+
402
+ def test_main_future_int_default(self) -> None:
403
+ obj = DataClassFutureIntDefault()
404
+ serialized = serialize_dataclass(obj)
405
+ result = parse_dataclass(serialized, DataClassFutureIntDefault)
406
+ assert result == obj
407
+
408
+ @given(int_=integers())
409
+ def test_extra_type(self, *, int_: int) -> None:
410
+ obj = DataClassFutureNestedOuterFirstOuter(
411
+ inner=DataClassFutureNestedOuterFirstInner(int_=int_)
412
+ )
413
+
414
+ def serializer(obj: DataClassFutureNestedOuterFirstInner, /) -> str:
415
+ return serialize_dataclass(obj)
416
+
417
+ serialized = serialize_dataclass(
418
+ obj, extra_serializers={DataClassFutureNestedOuterFirstInner: serializer}
419
+ )
420
+ result = parse_dataclass(
421
+ serialized,
422
+ DataClassFutureNestedOuterFirstOuter,
423
+ globalns=globals(),
424
+ extra_parsers={
425
+ DataClassFutureNestedOuterFirstInner: lambda text: parse_dataclass(
426
+ text, DataClassFutureNestedOuterFirstInner
427
+ )
428
+ },
429
+ )
430
+ assert result == obj
431
+
432
+ @given(key=sampled_from(["int_", "INT_"]), int_=integers())
433
+ def test_parse_text_case_insensitive(self, *, key: str, int_: int) -> None:
434
+ result = parse_dataclass(f"{key}={int_}", DataClassFutureInt)
435
+ expected = DataClassFutureInt(int_=int_)
436
+ assert result == expected
437
+
438
+ @given(int_=integers())
439
+ def test_parse_text_case_sensitive(self, *, int_: int) -> None:
440
+ result = parse_dataclass(
441
+ f"int_={int_}", DataClassFutureInt, case_sensitive=True
442
+ )
443
+ expected = DataClassFutureInt(int_=int_)
444
+ assert result == expected
445
+
446
+ @given(key=sampled_from(["int_", "INT_"]), int_=integers())
447
+ def test_parse_mapping_case_insensitive(self, *, key: str, int_: int) -> None:
448
+ result = parse_dataclass({key: str(int_)}, DataClassFutureInt)
449
+ expected = DataClassFutureInt(int_=int_)
450
+ assert result == expected
451
+
452
+ @given(int_=integers())
453
+ def test_parse_mapping_case_sensitive(self, *, int_: int) -> None:
454
+ result = parse_dataclass(
455
+ {"int_": str(int_)}, DataClassFutureInt, case_sensitive=True
456
+ )
457
+ expected = DataClassFutureInt(int_=int_)
458
+ assert result == expected
459
+
460
+ def test_parser_split_key_value_pairs_split(self) -> None:
461
+ with raises(
462
+ _ParseDataClassSplitKeyValuePairsSplitError,
463
+ match="Unable to construct 'DataClassFutureInt'; failed to split key-value pair 'bbb'",
464
+ ):
465
+ _ = parse_dataclass("a=1,bbb,c=333", DataClassFutureInt)
466
+
467
+ def test_error_parse_split_key_value_pairs_duplicate(self) -> None:
468
+ with raises(
469
+ _ParseDataClassSplitKeyValuePairsDuplicateKeysError,
470
+ match=r"Unable to construct 'DataClassFutureInt' since there are duplicate keys; got \{'b': 2\}",
471
+ ):
472
+ _ = parse_dataclass("a=1,b=22a,b=22b,c=3", DataClassFutureInt)
473
+
474
+ def test_error_parse_value(self) -> None:
475
+ with raises(
476
+ _ParseDataClassParseValueError,
477
+ match="Unable to construct 'DataClassFutureInt'; unable to parse field 'int_' of type <class 'int'>; got 'invalid'",
478
+ ):
479
+ _ = parse_dataclass("int_=invalid", DataClassFutureInt)
480
+
481
+
385
482
  class TestStrMappingToFieldMapping:
386
483
  @given(key=sampled_from(["int_", "INT_"]), int_=integers())
387
484
  def test_main_text_case_insensitive(self, *, key: str, int_: int) -> None:
@@ -467,70 +564,6 @@ class TestStrMappingToFieldMapping:
467
564
  )
468
565
 
469
566
 
470
- class TestSerializeAndParseDataClass:
471
- @given(int_=integers())
472
- def test_main_future_int(self, *, int_: int) -> None:
473
- obj = DataClassFutureInt(int_=int_)
474
- serialized = serialize_dataclass(obj)
475
- result = parse_dataclass(serialized, DataClassFutureInt)
476
- assert result == obj
477
-
478
- def test_main_future_int_default(self) -> None:
479
- obj = DataClassFutureIntDefault()
480
- serialized = serialize_dataclass(obj)
481
- result = parse_dataclass(serialized, DataClassFutureIntDefault)
482
- assert result == obj
483
-
484
- @given(key=sampled_from(["int_", "INT_"]), int_=integers())
485
- def test_parse_text_case_insensitive(self, *, key: str, int_: int) -> None:
486
- result = parse_dataclass(f"{key}={int_}", DataClassFutureInt)
487
- expected = DataClassFutureInt(int_=int_)
488
- assert result == expected
489
-
490
- @given(int_=integers())
491
- def test_parse_text_case_sensitive(self, *, int_: int) -> None:
492
- result = parse_dataclass(
493
- f"int_={int_}", DataClassFutureInt, case_sensitive=True
494
- )
495
- expected = DataClassFutureInt(int_=int_)
496
- assert result == expected
497
-
498
- @given(key=sampled_from(["int_", "INT_"]), int_=integers())
499
- def test_parse_mapping_case_insensitive(self, *, key: str, int_: int) -> None:
500
- result = parse_dataclass({key: str(int_)}, DataClassFutureInt)
501
- expected = DataClassFutureInt(int_=int_)
502
- assert result == expected
503
-
504
- @given(int_=integers())
505
- def test_parse_mapping_case_sensitive(self, *, int_: int) -> None:
506
- result = parse_dataclass(
507
- {"int_": str(int_)}, DataClassFutureInt, case_sensitive=True
508
- )
509
- expected = DataClassFutureInt(int_=int_)
510
- assert result == expected
511
-
512
- def test_parser_split_key_value_pairs_split(self) -> None:
513
- with raises(
514
- _ParseDataClassSplitKeyValuePairsSplitError,
515
- match="Unable to construct 'DataClassFutureInt'; failed to split key-value pair 'bbb'",
516
- ):
517
- _ = parse_dataclass("a=1,bbb,c=333", DataClassFutureInt)
518
-
519
- def test_error_parse_split_key_value_pairs_duplicate(self) -> None:
520
- with raises(
521
- _ParseDataClassSplitKeyValuePairsDuplicateKeysError,
522
- match=r"Unable to construct 'DataClassFutureInt' since there are duplicate keys; got \{'b': 2\}",
523
- ):
524
- _ = parse_dataclass("a=1,b=22a,b=22b,c=3", DataClassFutureInt)
525
-
526
- def test_error_parse_value(self) -> None:
527
- with raises(
528
- _ParseDataClassParseValueError,
529
- match="Unable to construct 'DataClassFutureInt'; unable to parse field 'int_' of type <class 'int'>; got 'invalid'",
530
- ):
531
- _ = parse_dataclass("int_=invalid", DataClassFutureInt)
532
-
533
-
534
567
  class TestYieldFields:
535
568
  def test_class_no_future_int(self) -> None:
536
569
  result = one(yield_fields(DataClassNoFutureInt))
@@ -147,6 +147,7 @@ class TestGetLogRecords:
147
147
  ),
148
148
  root=temp_paths(),
149
149
  )
150
+ @mark.flaky
150
151
  def test_dataframe(
151
152
  self, *, root: Path, items: Sequence[tuple[LogLevel, str, StrMapping]]
152
153
  ) -> None:
@@ -2,7 +2,6 @@ from __future__ import annotations
2
2
 
3
3
  import datetime as dt
4
4
  from collections.abc import Iterable
5
- from dataclasses import dataclass
6
5
  from pathlib import Path
7
6
  from types import NoneType
8
7
  from typing import Final, Literal
@@ -25,10 +24,13 @@ from pytest import raises
25
24
  from tests.test_operator import TruthEnum
26
25
  from tests.test_typing_funcs.with_future import (
27
26
  DataClassFutureInt,
27
+ DataClassFutureIntChild,
28
28
  DataClassFutureIntEven,
29
29
  DataClassFutureIntEvenOrOddTypeUnion,
30
30
  DataClassFutureIntEvenOrOddUnion,
31
31
  DataClassFutureIntOdd,
32
+ DataClassFutureIntParentFirst,
33
+ DataClassFutureIntParentSecond,
32
34
  TrueOrFalseFutureLit,
33
35
  TrueOrFalseFutureTypeLit,
34
36
  )
@@ -48,6 +50,8 @@ from utilities.math import is_equal
48
50
  from utilities.parse import (
49
51
  _ParseObjectExtraNonUniqueError,
50
52
  _ParseObjectParseError,
53
+ _SerializeObjectExtraNonUniqueError,
54
+ _SerializeObjectSerializeError,
51
55
  parse_object,
52
56
  serialize_object,
53
57
  )
@@ -99,11 +103,7 @@ class TestSerializeAndParseObject:
99
103
  result = parse_object(
100
104
  DataClassFutureInt,
101
105
  serialized,
102
- extra={
103
- DataClassFutureInt: lambda serialized: DataClassFutureInt(
104
- int_=int(serialized)
105
- )
106
- },
106
+ extra={DataClassFutureInt: lambda text: DataClassFutureInt(int_=int(text))},
107
107
  )
108
108
  expected = DataClassFutureInt(int_=value)
109
109
  assert result == expected
@@ -240,7 +240,7 @@ class TestSerializeAndParseObject:
240
240
 
241
241
  @given(value=integers())
242
242
  def test_type_union_with_extra(self, *, value: int) -> None:
243
- def parse_even_or_odd(text: str, /) -> DataClassFutureIntEvenOrOddTypeUnion:
243
+ def parser(text: str, /) -> DataClassFutureIntEvenOrOddTypeUnion:
244
244
  value = int(text)
245
245
  match value % 2:
246
246
  case 0:
@@ -254,7 +254,7 @@ class TestSerializeAndParseObject:
254
254
  result = parse_object(
255
255
  DataClassFutureIntEvenOrOddTypeUnion,
256
256
  serialized,
257
- extra={DataClassFutureIntEvenOrOddTypeUnion: parse_even_or_odd},
257
+ extra={DataClassFutureIntEvenOrOddTypeUnion: parser},
258
258
  )
259
259
  match value % 2:
260
260
  case 0:
@@ -267,7 +267,7 @@ class TestSerializeAndParseObject:
267
267
 
268
268
  @given(value=integers())
269
269
  def test_union_with_extra(self, *, value: int) -> None:
270
- def parse_even_or_odd(text: str, /) -> DataClassFutureIntEvenOrOddUnion:
270
+ def parser(text: str, /) -> DataClassFutureIntEvenOrOddUnion:
271
271
  value = int(text)
272
272
  match value % 2:
273
273
  case 0:
@@ -281,7 +281,7 @@ class TestSerializeAndParseObject:
281
281
  result = parse_object(
282
282
  DataClassFutureIntEvenOrOddUnion,
283
283
  serialized,
284
- extra={DataClassFutureIntEvenOrOddUnion: parse_even_or_odd},
284
+ extra={DataClassFutureIntEvenOrOddUnion: parser},
285
285
  )
286
286
  match value % 2:
287
287
  case 0:
@@ -299,7 +299,7 @@ class TestSerializeAndParseObject:
299
299
  assert result == version
300
300
 
301
301
 
302
- class TestParseSerialized:
302
+ class TestParseObject:
303
303
  def test_error_bool(self) -> None:
304
304
  with raises(
305
305
  _ParseObjectParseError,
@@ -357,27 +357,20 @@ class TestParseSerialized:
357
357
 
358
358
  @given(value=integers())
359
359
  def test_error_extra_non_unique(self, *, value: int) -> None:
360
- @dataclass(kw_only=True)
361
- class Parent1:
362
- x: int = 0
363
-
364
- @dataclass(kw_only=True)
365
- class Parent2:
366
- y: int = 0
367
-
368
- @dataclass(kw_only=True)
369
- class Child(Parent1, Parent2): ...
370
-
371
360
  with raises(
372
361
  _ParseObjectExtraNonUniqueError,
373
362
  match="Unable to parse <class '.*'> since `extra` must contain exactly one parent class; got <function .*>, <function .*> and perhaps more",
374
363
  ):
375
364
  _ = parse_object(
376
- Child,
365
+ DataClassFutureIntChild,
377
366
  serialize_object(value),
378
367
  extra={
379
- Parent1: lambda serialized: Child(x=int(serialized)),
380
- Parent2: lambda serialized: Child(y=int(serialized)),
368
+ DataClassFutureIntParentFirst: lambda text: DataClassFutureIntChild(
369
+ int1=int(text), int2=0
370
+ ),
371
+ DataClassFutureIntParentSecond: lambda text: DataClassFutureIntChild(
372
+ int1=0, int2=int(text)
373
+ ),
381
374
  },
382
375
  )
383
376
 
@@ -538,6 +531,33 @@ class TestParseSerialized:
538
531
 
539
532
 
540
533
  class TestSerializeObject:
534
+ def test_error_extra_empty(self) -> None:
535
+ with raises(
536
+ _SerializeObjectSerializeError,
537
+ match=r"Unable to serialize object typing\.Final",
538
+ ):
539
+ _ = serialize_object(Final, extra={})
540
+
541
+ @given(int1=integers(), int2=integers())
542
+ def test_error_extra_non_unique(self, *, int1: int, int2: int) -> None:
543
+ def serializer1(obj: DataClassFutureIntParentFirst, /) -> str:
544
+ return str(obj.int1)
545
+
546
+ def serializer2(obj: DataClassFutureIntParentSecond, /) -> str:
547
+ return str(obj.int2)
548
+
549
+ with raises(
550
+ _SerializeObjectExtraNonUniqueError,
551
+ match=r"Unable to serialize object DataClassFutureIntChild\(.*\) since `extra` must contain exactly one parent class; got <function .*>, <function .*> and perhaps more",
552
+ ):
553
+ _ = serialize_object(
554
+ DataClassFutureIntChild(int1=int1, int2=int2),
555
+ extra={
556
+ DataClassFutureIntParentFirst: serializer1,
557
+ DataClassFutureIntParentSecond: serializer2,
558
+ },
559
+ )
560
+
541
561
  def test_error_not_implemented(self) -> None:
542
- with raises(NotImplementedError):
562
+ with raises(_SerializeObjectSerializeError):
543
563
  _ = serialize_object(Final)
@@ -91,6 +91,7 @@ class TestSplitKeyValuePairs:
91
91
  ("a=1,=22,c=333", [("a", "1"), ("", "22"), ("c", "333")]),
92
92
  ("a=1,b=,c=333", [("a", "1"), ("b", ""), ("c", "333")]),
93
93
  ("a=1,b=(22,22,22),c=333", [("a", "1"), ("b", "(22,22,22)"), ("c", "333")]),
94
+ ("a=1,b=(c=22),c=333", [("a", "1"), ("b", "(c=22)"), ("c", "333")]),
94
95
  ])
95
96
  )
96
97
  def test_main(self, *, case: tuple[str, Sequence[tuple[str, str]]]) -> None:
@@ -87,6 +87,22 @@ class DataClassFutureIntOneAndTwo:
87
87
  int2: int
88
88
 
89
89
 
90
+ @dataclass(order=True, unsafe_hash=True, kw_only=True)
91
+ class DataClassFutureIntParentFirst:
92
+ int1: int
93
+
94
+
95
+ @dataclass(order=True, unsafe_hash=True, kw_only=True)
96
+ class DataClassFutureIntParentSecond:
97
+ int2: int
98
+
99
+
100
+ @dataclass(order=True, unsafe_hash=True, kw_only=True)
101
+ class DataClassFutureIntChild(
102
+ DataClassFutureIntParentFirst, DataClassFutureIntParentSecond
103
+ ): ...
104
+
105
+
90
106
  @dataclass(order=True, unsafe_hash=True, kw_only=True)
91
107
  class DataClassFutureListInts:
92
108
  ints: list[int]
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.110.3"
3
+ __version__ = "0.110.4"
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from collections.abc import Mapping
4
+ from contextlib import suppress
4
5
  from dataclasses import MISSING, dataclass, field, fields, replace
5
6
  from typing import (
6
7
  TYPE_CHECKING,
@@ -22,16 +23,22 @@ from utilities.functions import (
22
23
  from utilities.iterables import OneStrEmptyError, OneStrNonUniqueError, one_str
23
24
  from utilities.operator import is_equal
24
25
  from utilities.parse import ParseObjectError, parse_object, serialize_object
26
+ from utilities.re import ExtractGroupError, extract_group
25
27
  from utilities.sentinel import Sentinel, sentinel
26
28
  from utilities.text import (
29
+ BRACKETS,
27
30
  LIST_SEPARATOR,
28
31
  PAIR_SEPARATOR,
29
32
  _SplitKeyValuePairsDuplicateKeysError,
30
33
  _SplitKeyValuePairsSplitError,
31
- join_strs,
32
34
  split_key_value_pairs,
33
35
  )
34
- from utilities.types import ParseObjectExtra, StrStrMapping, TDataclass
36
+ from utilities.types import (
37
+ ParseObjectExtra,
38
+ SerializeObjectExtra,
39
+ StrStrMapping,
40
+ TDataclass,
41
+ )
35
42
  from utilities.typing import get_type_hints
36
43
 
37
44
  if TYPE_CHECKING:
@@ -404,10 +411,11 @@ def serialize_dataclass(
404
411
  exclude: Iterable[str] | None = None,
405
412
  rel_tol: float | None = None,
406
413
  abs_tol: float | None = None,
407
- extra: Mapping[type[_U], Callable[[_U, _U], bool]] | None = None,
414
+ extra_equal: Mapping[type[_U], Callable[[_U, _U], bool]] | None = None,
408
415
  defaults: bool = False,
409
416
  list_separator: str = LIST_SEPARATOR,
410
417
  pair_separator: str = PAIR_SEPARATOR,
418
+ extra_serializers: SerializeObjectExtra | None = None,
411
419
  ) -> str:
412
420
  """Serialize a Dataclass."""
413
421
  mapping: StrStrMapping = {}
@@ -422,16 +430,21 @@ def serialize_dataclass(
422
430
  exclude=exclude,
423
431
  rel_tol=rel_tol,
424
432
  abs_tol=abs_tol,
425
- extra=extra,
433
+ extra=extra_equal,
426
434
  defaults=defaults,
427
435
  ):
428
436
  mapping[fld.name] = serialize_object(
429
- fld.value, list_separator=list_separator, pair_separator=pair_separator
437
+ fld.value,
438
+ list_separator=list_separator,
439
+ pair_separator=pair_separator,
440
+ extra=extra_serializers,
430
441
  )
431
- joined_items = (
432
- join_strs(item, separator=pair_separator) for item in mapping.items()
442
+ return serialize_object(
443
+ mapping,
444
+ list_separator=list_separator,
445
+ pair_separator=pair_separator,
446
+ extra=extra_serializers,
433
447
  )
434
- return join_strs(joined_items, separator=list_separator)
435
448
 
436
449
 
437
450
  def parse_dataclass(
@@ -441,6 +454,7 @@ def parse_dataclass(
441
454
  *,
442
455
  list_separator: str = LIST_SEPARATOR,
443
456
  pair_separator: str = PAIR_SEPARATOR,
457
+ brackets: Iterable[tuple[str, str]] | None = BRACKETS,
444
458
  globalns: StrMapping | None = None,
445
459
  localns: StrMapping | None = None,
446
460
  warn_name_errors: bool = False,
@@ -453,7 +467,11 @@ def parse_dataclass(
453
467
  match text_or_mapping:
454
468
  case str() as text:
455
469
  keys_to_serializes = _parse_dataclass_split_key_value_pairs(
456
- text, cls, list_separator=list_separator, pair_separator=pair_separator
470
+ text,
471
+ cls,
472
+ list_separator=list_separator,
473
+ pair_separator=pair_separator,
474
+ brackets=brackets,
457
475
  )
458
476
  case Mapping() as keys_to_serializes:
459
477
  ...
@@ -477,7 +495,14 @@ def parse_dataclass(
477
495
  )
478
496
  field_names_to_values = {
479
497
  f.name: _parse_dataclass_parse_text(
480
- f, t, cls, head=head, case_sensitive=case_sensitive, extra=extra_parsers
498
+ f,
499
+ t,
500
+ cls,
501
+ list_separator=list_separator,
502
+ pair_separator=pair_separator,
503
+ head=head,
504
+ case_sensitive=case_sensitive,
505
+ extra=extra_parsers,
481
506
  )
482
507
  for f, t in fields_to_serializes.items()
483
508
  }
@@ -501,12 +526,16 @@ def _parse_dataclass_split_key_value_pairs(
501
526
  *,
502
527
  list_separator: str = LIST_SEPARATOR,
503
528
  pair_separator: str = PAIR_SEPARATOR,
529
+ brackets: Iterable[tuple[str, str]] | None = BRACKETS,
504
530
  ) -> StrStrMapping:
531
+ with suppress(ExtractGroupError):
532
+ text = extract_group(r"^\{?(.*?)\}?$", text)
505
533
  try:
506
534
  return split_key_value_pairs(
507
535
  text,
508
536
  list_separator=list_separator,
509
537
  pair_separator=pair_separator,
538
+ brackets=brackets,
510
539
  mapping=True,
511
540
  )
512
541
  except _SplitKeyValuePairsSplitError as error:
@@ -31,7 +31,7 @@ from utilities.text import (
31
31
  split_key_value_pairs,
32
32
  split_str,
33
33
  )
34
- from utilities.types import Duration, Number, ParseObjectExtra
34
+ from utilities.types import Duration, Number, ParseObjectExtra, SerializeObjectExtra
35
35
  from utilities.typing import (
36
36
  get_args,
37
37
  is_dict_type,
@@ -240,7 +240,7 @@ def _parse_object_type(
240
240
  ) from None
241
241
  else:
242
242
  return parser(text)
243
- raise _ParseObjectParseError(type_=cls, text=text) from None
243
+ raise _ParseObjectParseError(type_=cls, text=text)
244
244
 
245
245
 
246
246
  def _parse_object_dict_type(
@@ -466,6 +466,7 @@ def serialize_object(
466
466
  *,
467
467
  list_separator: str = LIST_SEPARATOR,
468
468
  pair_separator: str = PAIR_SEPARATOR,
469
+ extra: SerializeObjectExtra | None = None,
469
470
  ) -> str:
470
471
  """Convert an object to text."""
471
472
  if (obj is None) or isinstance(
@@ -506,7 +507,9 @@ def serialize_object(
506
507
  return _serialize_object_set(
507
508
  obj, list_separator=list_separator, pair_separator=pair_separator
508
509
  )
509
- raise NotImplementedError(obj)
510
+ if extra is not None:
511
+ return _serialize_object_extra(obj, extra)
512
+ raise _SerializeObjectSerializeError(obj=obj)
510
513
 
511
514
 
512
515
  def _serialize_object_dict(
@@ -534,6 +537,19 @@ def _serialize_object_dict(
534
537
  return f"{{{joined}}}"
535
538
 
536
539
 
540
+ def _serialize_object_extra(obj: Any, extra: SerializeObjectExtra, /) -> str:
541
+ try:
542
+ serializer = one(s for c, s in extra.items() if isinstance(obj, c))
543
+ except OneEmptyError:
544
+ raise _SerializeObjectSerializeError(obj=obj) from None
545
+ except OneNonUniqueError as error:
546
+ raise _SerializeObjectExtraNonUniqueError(
547
+ obj=obj, first=error.first, second=error.second
548
+ ) from None
549
+ else:
550
+ return serializer(obj)
551
+
552
+
537
553
  def _serialize_object_list(
538
554
  obj: Sequence[Any],
539
555
  /,
@@ -585,4 +601,25 @@ def _serialize_object_tuple(
585
601
  return f"({joined})"
586
602
 
587
603
 
604
+ @dataclass(kw_only=True, slots=True)
605
+ class SerializeObjectError(Exception):
606
+ obj: Any
607
+
608
+
609
+ class _SerializeObjectSerializeError(SerializeObjectError):
610
+ @override
611
+ def __str__(self) -> str:
612
+ return f"Unable to serialize object {self.obj!r}"
613
+
614
+
615
+ @dataclass
616
+ class _SerializeObjectExtraNonUniqueError(SerializeObjectError):
617
+ first: type[Any]
618
+ second: type[Any]
619
+
620
+ @override
621
+ def __str__(self) -> str:
622
+ return f"Unable to serialize object {self.obj!r} since `extra` must contain exactly one parent class; got {self.first!r}, {self.second!r} and perhaps more"
623
+
624
+
588
625
  __all__ = ["parse_object", "serialize_object"]
@@ -144,7 +144,10 @@ def split_key_value_pairs(
144
144
  except SplitStrError as error:
145
145
  raise _SplitKeyValuePairsSplitError(text=text, inner=error.text) from None
146
146
  try:
147
- pairs = [split_str(text_i, separator=pair_separator, n=2) for text_i in texts]
147
+ pairs = [
148
+ split_str(text_i, separator=pair_separator, brackets=brackets, n=2)
149
+ for text_i in texts
150
+ ]
148
151
  except SplitStrError as error:
149
152
  raise _SplitKeyValuePairsSplitError(text=text, inner=error.text) from None
150
153
  if not mapping:
@@ -232,6 +232,7 @@ class SupportsRound(Protocol[_T_co]):
232
232
 
233
233
  # parse
234
234
  type ParseObjectExtra = Mapping[Any, Callable[[str], Any]]
235
+ type SerializeObjectExtra = Mapping[type[Any], Callable[[Any], str]]
235
236
 
236
237
 
237
238
  # pathlib
@@ -288,6 +289,7 @@ __all__ = [
288
289
  "PathLikeOrCallable",
289
290
  "RoundMode",
290
291
  "Seed",
292
+ "SerializeObjectExtra",
291
293
  "StrMapping",
292
294
  "StrStrMapping",
293
295
  "SupportsAbs",