dycw-utilities 0.146.8__tar.gz → 0.147.0__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 (219) hide show
  1. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/PKG-INFO +1 -1
  2. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/pyproject.toml +2 -6
  3. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_whenever.py +0 -16
  4. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/__init__.py +1 -1
  5. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/whenever.py +2 -27
  6. dycw_utilities-0.146.8/src/tests/test_aiolimiter.py +0 -40
  7. dycw_utilities-0.146.8/src/tests/test_pydantic.py +0 -57
  8. dycw_utilities-0.146.8/src/tests/test_python_dotenv.py +0 -125
  9. dycw_utilities-0.146.8/src/tests/test_streamlit.py +0 -6
  10. dycw_utilities-0.146.8/src/utilities/aiolimiter.py +0 -25
  11. dycw_utilities-0.146.8/src/utilities/pydantic.py +0 -58
  12. dycw_utilities-0.146.8/src/utilities/python_dotenv.py +0 -101
  13. dycw_utilities-0.146.8/src/utilities/streamlit.py +0 -105
  14. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/.gitignore +0 -0
  15. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/LICENSE +0 -0
  16. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/README.md +0 -0
  17. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/__init__.py +0 -0
  18. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/conftest.py +0 -0
  19. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/modules/__init__.py +0 -0
  20. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/modules/package_missing/__init__.py +0 -0
  21. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/modules/package_missing/module.py +0 -0
  22. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/modules/package_with/__init__.py +0 -0
  23. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/modules/package_with/outer_1.py +0 -0
  24. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/modules/package_with/outer_2.py +0 -0
  25. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/modules/package_with/subpackage/__init__.py +0 -0
  26. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/modules/package_with/subpackage/inner_1.py +0 -0
  27. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/modules/package_with/subpackage/inner_2.py +0 -0
  28. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/modules/package_with/subpackage/inner_3.py +0 -0
  29. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/modules/package_without/__init__.py +0 -0
  30. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/modules/package_without/module_1.py +0 -0
  31. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/modules/package_without/module_2.py +0 -0
  32. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/modules/standalone.py +0 -0
  33. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/modules/with_imports.py +0 -0
  34. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -0
  35. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -0
  36. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -0
  37. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -0
  38. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -0
  39. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -0
  40. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -0
  41. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -0
  42. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_altair.py +0 -0
  43. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_asyncio.py +0 -0
  44. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_atomicwrites.py +0 -0
  45. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_atools.py +0 -0
  46. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_cachetools.py +0 -0
  47. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_click.py +0 -0
  48. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_concurrent.py +0 -0
  49. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_contextlib.py +0 -0
  50. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_contextvars.py +0 -0
  51. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_cryptography.py +0 -0
  52. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_cvxpy.py +0 -0
  53. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_dataclasses.py +0 -0
  54. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_enum.py +0 -0
  55. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_errors.py +0 -0
  56. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_eventkit.py +0 -0
  57. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_fastapi.py +0 -0
  58. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_fpdf2.py +0 -0
  59. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_functions.py +0 -0
  60. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_functools.py +0 -0
  61. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_getpass.py +0 -0
  62. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_gzip.py +0 -0
  63. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_hashlib.py +0 -0
  64. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_http.py +0 -0
  65. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_hypothesis.py +0 -0
  66. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_importlib.py +0 -0
  67. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_inflect.py +0 -0
  68. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_ipython.py +0 -0
  69. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_iterables.py +0 -0
  70. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_json.py +0 -0
  71. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_jupyter.py +0 -0
  72. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_libcst.py +0 -0
  73. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_lightweight_charts.py +0 -0
  74. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_logging.py +0 -0
  75. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_math.py +0 -0
  76. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_memory_profiler.py +0 -0
  77. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_modules.py +0 -0
  78. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_more_itertools.py +0 -0
  79. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_numpy.py +0 -0
  80. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_objects/__init__.py +0 -0
  81. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_objects/objects.py +0 -0
  82. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_operator.py +0 -0
  83. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_optuna.py +0 -0
  84. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_orjson.py +0 -0
  85. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_os.py +0 -0
  86. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_parse.py +0 -0
  87. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_pathlib.py +0 -0
  88. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_period.py +0 -0
  89. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_pickle.py +0 -0
  90. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_platform.py +0 -0
  91. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_polars.py +0 -0
  92. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_polars_ols.py +0 -0
  93. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_postgres.py +0 -0
  94. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_pottery.py +0 -0
  95. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_pqdm.py +0 -0
  96. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_psutil.py +0 -0
  97. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_pyinstrument.py +0 -0
  98. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_pytest.py +0 -0
  99. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_pytest_randomly.py +0 -0
  100. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_pytest_regressions.py +0 -0
  101. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_random.py +0 -0
  102. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_re.py +0 -0
  103. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_redis.py +0 -0
  104. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_reprlib.py +0 -0
  105. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_scipy.py +0 -0
  106. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_sentinel.py +0 -0
  107. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_shelve.py +0 -0
  108. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_slack_sdk.py +0 -0
  109. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_socket.py +0 -0
  110. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_sqlalchemy.py +0 -0
  111. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_sqlalchemy_polars.py +0 -0
  112. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_statsmodels.py +0 -0
  113. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_string.py +0 -0
  114. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_tempfile.py +0 -0
  115. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_text.py +0 -0
  116. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_threading.py +0 -0
  117. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_timer.py +0 -0
  118. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_traceback.py +0 -0
  119. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_typed_settings.py +0 -0
  120. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_types.py +0 -0
  121. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_typing.py +0 -0
  122. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_typing_funcs/__init__.py +0 -0
  123. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_typing_funcs/no_future.py +0 -0
  124. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_typing_funcs/with_future.py +0 -0
  125. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_tzdata.py +0 -0
  126. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_tzlocal.py +0 -0
  127. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_uuid.py +0 -0
  128. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_version.py +0 -0
  129. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_warnings.py +0 -0
  130. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_zipfile.py +0 -0
  131. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/tests/test_zoneinfo.py +0 -0
  132. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/altair.py +0 -0
  133. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/asyncio.py +0 -0
  134. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/atomicwrites.py +0 -0
  135. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/atools.py +0 -0
  136. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/cachetools.py +0 -0
  137. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/click.py +0 -0
  138. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/concurrent.py +0 -0
  139. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/contextlib.py +0 -0
  140. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/contextvars.py +0 -0
  141. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/cryptography.py +0 -0
  142. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/cvxpy.py +0 -0
  143. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/dataclasses.py +0 -0
  144. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/enum.py +0 -0
  145. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/errors.py +0 -0
  146. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/eventkit.py +0 -0
  147. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/fastapi.py +0 -0
  148. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/fpdf2.py +0 -0
  149. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/functions.py +0 -0
  150. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/functools.py +0 -0
  151. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/getpass.py +0 -0
  152. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/gzip.py +0 -0
  153. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/hashlib.py +0 -0
  154. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/http.py +0 -0
  155. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/hypothesis.py +0 -0
  156. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/importlib.py +0 -0
  157. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/inflect.py +0 -0
  158. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/ipython.py +0 -0
  159. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/iterables.py +0 -0
  160. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/json.py +0 -0
  161. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/jupyter.py +0 -0
  162. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/libcst.py +0 -0
  163. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/lightweight_charts.py +0 -0
  164. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/logging.py +0 -0
  165. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/math.py +0 -0
  166. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/memory_profiler.py +0 -0
  167. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/modules.py +0 -0
  168. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/more_itertools.py +0 -0
  169. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/numpy.py +0 -0
  170. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/operator.py +0 -0
  171. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/optuna.py +0 -0
  172. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/orjson.py +0 -0
  173. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/os.py +0 -0
  174. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/parse.py +0 -0
  175. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/pathlib.py +0 -0
  176. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/period.py +0 -0
  177. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/pickle.py +0 -0
  178. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/platform.py +0 -0
  179. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/polars.py +0 -0
  180. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/polars_ols.py +0 -0
  181. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/postgres.py +0 -0
  182. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/pottery.py +0 -0
  183. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/pqdm.py +0 -0
  184. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/psutil.py +0 -0
  185. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/py.typed +0 -0
  186. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/pyinstrument.py +0 -0
  187. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/pytest.py +0 -0
  188. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/pytest_plugins/__init__.py +0 -0
  189. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/pytest_plugins/pytest_randomly.py +0 -0
  190. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/pytest_plugins/pytest_regressions.py +0 -0
  191. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/pytest_regressions.py +0 -0
  192. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/random.py +0 -0
  193. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/re.py +0 -0
  194. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/redis.py +0 -0
  195. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/reprlib.py +0 -0
  196. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/scipy.py +0 -0
  197. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/sentinel.py +0 -0
  198. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/shelve.py +0 -0
  199. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/slack_sdk.py +0 -0
  200. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/socket.py +0 -0
  201. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/sqlalchemy.py +0 -0
  202. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/sqlalchemy_polars.py +0 -0
  203. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/statsmodels.py +0 -0
  204. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/string.py +0 -0
  205. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/tempfile.py +0 -0
  206. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/text.py +0 -0
  207. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/threading.py +0 -0
  208. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/timer.py +0 -0
  209. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/traceback.py +0 -0
  210. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/typed_settings.py +0 -0
  211. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/types.py +0 -0
  212. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/typing.py +0 -0
  213. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/tzdata.py +0 -0
  214. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/tzlocal.py +0 -0
  215. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/uuid.py +0 -0
  216. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/version.py +0 -0
  217. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/warnings.py +0 -0
  218. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/zipfile.py +0 -0
  219. {dycw_utilities-0.146.8 → dycw_utilities-0.147.0}/src/utilities/zoneinfo.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.146.8
3
+ Version: 0.147.0
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -7,7 +7,6 @@ requires = ["hatchling"]
7
7
 
8
8
  # dependency groups
9
9
  [dependency-groups]
10
- aiolimiter = ["aiolimiter >= 1.2.1, < 1.3"]
11
10
  altair = ["altair >= 5.5.0, < 5.6"]
12
11
  altair-test = [
13
12
  "polars",
@@ -64,7 +63,6 @@ pottery = ["pottery >= 3.0.1, < 3.1"]
64
63
  pottery-test = ["orjson", "pytest-rerunfailures"]
65
64
  pqdm = ["pqdm >= 0.2.0, < 0.3"]
66
65
  psutil = ["psutil >= 7.0.0, < 7.1"]
67
- pydantic = ["pydantic >= 2.11.4, < 2.12"]
68
66
  pyinstrument = ["pyinstrument >= 5.0.3, < 5.1"]
69
67
  pytest = [
70
68
  "pudb >= 2025.1, < 2025.2",
@@ -77,7 +75,6 @@ pytest = [
77
75
  pytest-regressions = ["pytest-regressions >= 2.8.1, < 2.9"]
78
76
  pytest-regressions-test = ["orjson", "polars"]
79
77
  pytest-test = ["orjson", "pytest-rng", "pytest-rerunfailures"]
80
- python-dotenv = ["python-dotenv >= 1.1.1, < 1.2"]
81
78
  redis = ["redis >= 6.2.0, < 6.3", "orjson"]
82
79
  redis-test = ["pytest-rerunfailures"]
83
80
  reprlib-test = ["rich"]
@@ -90,7 +87,6 @@ sqlalchemy-polars = ["sqlalchemy", "polars"]
90
87
  sqlalchemy-polars-test = ["aiosqlite", "asyncpg", "greenlet"]
91
88
  sqlalchemy-test = ["aiosqlite", "asyncpg", "greenlet"]
92
89
  statsmodels = ["statsmodels >= 0.14.4, < 0.15"]
93
- streamlit = ["streamlit >= 1.46.0, < 1.47"]
94
90
  typed-settings = ["typed-settings >= 24.6.0, < 24.7"]
95
91
  tzdata = ["tzdata >= 2025.2, < 2025.3"]
96
92
 
@@ -106,7 +102,7 @@ dependencies = [
106
102
  name = "dycw-utilities"
107
103
  readme = "README.md"
108
104
  requires-python = ">= 3.12"
109
- version = "0.146.8"
105
+ version = "0.147.0"
110
106
 
111
107
  [project.entry-points.pytest11]
112
108
  pytest-randomly = "utilities.pytest_plugins.pytest_randomly"
@@ -139,7 +135,7 @@ test = [
139
135
  # bump-my-version
140
136
  [tool.bumpversion]
141
137
  allow_dirty = true
142
- current_version = "0.146.8"
138
+ current_version = "0.147.0"
143
139
 
144
140
  [[tool.bumpversion.files]]
145
141
  filename = "src/utilities/__init__.py"
@@ -65,8 +65,6 @@ from utilities.whenever import (
65
65
  ToNanosecondsError,
66
66
  ToPyTimeDeltaError,
67
67
  WheneverLogRecord,
68
- _MinMaxDateMaxDateError,
69
- _MinMaxDateMinDateError,
70
68
  _MinMaxDatePeriodError,
71
69
  _RoundDateOrDateTimeDateTimeIntraDayWithWeekdayError,
72
70
  _RoundDateOrDateTimeDateWithIntradayDeltaError,
@@ -464,20 +462,6 @@ class TestMinMaxDate:
464
462
  if (min_date_use is not None) and (max_date_use is not None):
465
463
  assert min_date_use <= max_date_use
466
464
 
467
- @given(date=dates(min_value=TODAY_UTC + DAY))
468
- def test_error_min_date(self, *, date: Date) -> None:
469
- with raises(
470
- _MinMaxDateMinDateError, match="Min date must be at most today; got .* > .*"
471
- ):
472
- _ = min_max_date(min_date=date)
473
-
474
- @given(date=dates(min_value=TODAY_UTC + DAY))
475
- def test_error_max_date(self, *, date: Date) -> None:
476
- with raises(
477
- _MinMaxDateMaxDateError, match="Max date must be at most today; got .* > .*"
478
- ):
479
- _ = min_max_date(max_date=date)
480
-
481
465
  @given(dates=pairs(dates(max_value=TODAY_UTC), unique=True, sorted=True))
482
466
  def test_error_period(self, *, dates: tuple[Date, Date]) -> None:
483
467
  with raises(
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.146.8"
3
+ __version__ = "0.147.0"
@@ -316,20 +316,16 @@ def min_max_date(
316
316
  max_age: DateDelta | None = None,
317
317
  time_zone: TimeZoneLike = UTC,
318
318
  ) -> tuple[Date | None, Date | None]:
319
- """Ucompute the min/max date given a combination of dates/ages."""
319
+ """Compute the min/max date given a combination of dates/ages."""
320
320
  today = get_today(time_zone=time_zone)
321
321
  min_parts: list[Date] = []
322
322
  if min_date is not None:
323
- if min_date > today:
324
- raise _MinMaxDateMinDateError(min_date=min_date, today=today)
325
323
  min_parts.append(min_date)
326
324
  if max_age is not None:
327
325
  min_parts.append(today - max_age)
328
326
  min_date_use = max(min_parts, default=None)
329
327
  max_parts: list[Date] = []
330
328
  if max_date is not None:
331
- if max_date > today:
332
- raise _MinMaxDateMaxDateError(max_date=max_date, today=today)
333
329
  max_parts.append(max_date)
334
330
  if min_age is not None:
335
331
  max_parts.append(today - min_age)
@@ -344,34 +340,13 @@ def min_max_date(
344
340
 
345
341
 
346
342
  @dataclass(kw_only=True, slots=True)
347
- class MinMaxDateError(Exception): ...
348
-
349
-
350
- @dataclass(kw_only=True, slots=True)
351
- class _MinMaxDateMinDateError(MinMaxDateError):
343
+ class MinMaxDateError(Exception):
352
344
  min_date: Date
353
- today: Date
354
-
355
- @override
356
- def __str__(self) -> str:
357
- return f"Min date must be at most today; got {self.min_date} > {self.today}"
358
-
359
-
360
- @dataclass(kw_only=True, slots=True)
361
- class _MinMaxDateMaxDateError(MinMaxDateError):
362
345
  max_date: Date
363
- today: Date
364
-
365
- @override
366
- def __str__(self) -> str:
367
- return f"Max date must be at most today; got {self.max_date} > {self.today}"
368
346
 
369
347
 
370
348
  @dataclass(kw_only=True, slots=True)
371
349
  class _MinMaxDatePeriodError(MinMaxDateError):
372
- min_date: Date
373
- max_date: Date
374
-
375
350
  @override
376
351
  def __str__(self) -> str:
377
352
  return (
@@ -1,40 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from asyncio import sleep
4
- from typing import ClassVar
5
-
6
- from utilities.aiolimiter import get_async_limiter
7
- from utilities.text import unique_str
8
- from utilities.timer import Timer
9
- from utilities.whenever import SECOND
10
-
11
-
12
- class TestGetAsyncLimiter:
13
- async def test_main(self) -> None:
14
- counter = 0
15
-
16
- async def increment() -> None:
17
- nonlocal counter
18
- counter += 1
19
- await sleep(0.01)
20
-
21
- name = unique_str()
22
- with Timer() as timer:
23
- for _ in range(2):
24
- async with get_async_limiter(name, rate=0.5):
25
- await increment()
26
- assert timer >= 0.48 * SECOND
27
-
28
- shared: ClassVar[str] = unique_str()
29
-
30
- async def test_shared1(self) -> None:
31
- async with get_async_limiter(self.shared):
32
- await sleep(0.01)
33
-
34
- async def test_shared2(self) -> None:
35
- async with get_async_limiter(self.shared):
36
- await sleep(0.01)
37
-
38
- async def test_shared3(self) -> None:
39
- async with get_async_limiter(self.shared):
40
- await sleep(0.01)
@@ -1,57 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from pathlib import Path
4
-
5
- from hypothesis import given
6
- from hypothesis.strategies import integers
7
- from pydantic import BaseModel
8
- from pytest import raises
9
-
10
- from utilities.hypothesis import temp_paths
11
- from utilities.pydantic import HashableBaseModel, LoadModelError, load_model, save_model
12
- from utilities.pytest import skipif_windows
13
-
14
-
15
- class TestHashableBaseModel:
16
- @given(x=integers())
17
- def test_main(self, *, x: int) -> None:
18
- class Example(HashableBaseModel):
19
- x: int
20
-
21
- example = Example(x=x)
22
- assert isinstance(hash(example), int)
23
-
24
-
25
- class TestSaveAndLoadModel:
26
- @given(x=integers(), root=temp_paths())
27
- def test_main(self, *, x: int, root: Path) -> None:
28
- path = Path(root, "model.json")
29
-
30
- class Example(BaseModel):
31
- x: int
32
-
33
- example = Example(x=x)
34
- save_model(example, path)
35
- loaded = load_model(Example, path)
36
- assert loaded == example
37
-
38
- @skipif_windows
39
- def test_load_model_error_dir(self, *, tmp_path: Path) -> None:
40
- path = tmp_path.joinpath("dir")
41
- path.mkdir()
42
-
43
- class Example(BaseModel):
44
- x: int
45
-
46
- with raises(
47
- LoadModelError,
48
- match=r"Unable to load .*; path '.*' must not be a directory\.",
49
- ):
50
- _ = load_model(Example, path)
51
-
52
- def test_load_model_error_file(self, *, tmp_path: Path) -> None:
53
- class Example(BaseModel):
54
- x: int
55
-
56
- with raises(LoadModelError, match=r"Unable to load .*; path '.*' must exist\."):
57
- _ = load_model(Example, tmp_path.joinpath("model.json"))
@@ -1,125 +0,0 @@
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
- _LoadSettingsFileNotFoundError,
18
- _LoadSettingsMissingKeysError,
19
- load_settings,
20
- )
21
-
22
- if TYPE_CHECKING:
23
- from pathlib import Path
24
-
25
-
26
- class TestLoadSettings:
27
- @given(
28
- data=data(),
29
- root=git_repos(),
30
- key_file=sampled_from(["key", "KEY"]),
31
- value_file=text_ascii(),
32
- use_env=booleans(),
33
- )
34
- @settings_with_reduced_examples()
35
- def test_main(
36
- self,
37
- *,
38
- data: DataObject,
39
- root: Path,
40
- key_file: str,
41
- value_file: str,
42
- use_env: bool,
43
- ) -> None:
44
- _ = root.joinpath(".env").write_text(f"{key_file} = {value_file}\n")
45
-
46
- @dataclass(kw_only=True, slots=True)
47
- class SettingsLower:
48
- key: str
49
-
50
- @dataclass(kw_only=True, slots=True)
51
- class SettingsUpper:
52
- KEY: str
53
-
54
- SettingsUse = data.draw(sampled_from([SettingsLower, SettingsUpper])) # noqa: N806
55
- if use_env:
56
- key_env = data.draw(sampled_from(["key", "KEY"]))
57
- value_env = data.draw(text_ascii())
58
- with temp_environ({key_env: value_env}):
59
- settings = load_settings(SettingsUse, path=root)
60
- exp_value = value_env
61
- else:
62
- settings = load_settings(SettingsUse, path=root)
63
- exp_value = value_file
64
-
65
- if SettingsUse is SettingsLower:
66
- expected = SettingsLower(key=exp_value)
67
- elif SettingsUse is SettingsUpper:
68
- expected = SettingsUpper(KEY=exp_value)
69
- else:
70
- raise ImpossibleCaseError(case=[f"{SettingsUse=}"])
71
- assert settings == expected
72
-
73
- @given(root=git_repos(), value=text_ascii())
74
- @settings_with_reduced_examples()
75
- def test_file_extra_key(self, *, root: Path, value: str) -> None:
76
- @dataclass(kw_only=True, slots=True)
77
- class Settings:
78
- key: str
79
-
80
- _ = root.joinpath(".env").write_text(f"key = {value}\nother = {value}\n")
81
- settings = load_settings(Settings, path=root)
82
- expected = Settings(key=value)
83
- assert settings == expected
84
-
85
- @given(root=git_repos())
86
- @settings_with_reduced_examples()
87
- def test_error_file_not_found(self, *, root: Path) -> None:
88
- @dataclass(kw_only=True, slots=True)
89
- class Settings:
90
- KEY: str
91
-
92
- with raises(_LoadSettingsFileNotFoundError, match=r"Path '.*' must exist"):
93
- _ = load_settings(Settings, path=root)
94
-
95
- @given(root=git_repos(), value=integers())
96
- @settings_with_reduced_examples()
97
- def test_error_duplicate_keys(self, *, root: Path, value: int) -> None:
98
- @dataclass(kw_only=True, slots=True)
99
- class Settings:
100
- key: str
101
-
102
- _ = root.joinpath(".env").write_text(f"key = {value}\nKEY = {value}\n")
103
- with raises(
104
- _LoadSettingsDuplicateKeysError,
105
- match=re.compile(
106
- r"Mapping .* keys must not contain duplicates \(modulo case\); got .*",
107
- flags=DOTALL,
108
- ),
109
- ):
110
- _ = load_settings(Settings, path=root)
111
-
112
- @given(root=git_repos())
113
- @settings_with_reduced_examples()
114
- def test_error_missing_keys(self, *, root: Path) -> None:
115
- @dataclass(kw_only=True, slots=True)
116
- class Settings:
117
- key: str
118
-
119
- root.joinpath(".env").touch()
120
-
121
- with raises(
122
- _LoadSettingsMissingKeysError,
123
- match=r"Unable to load '.*'; missing value\(s\) for 'key'",
124
- ):
125
- _ = load_settings(Settings, path=root)
@@ -1,6 +0,0 @@
1
- from __future__ import annotations
2
-
3
-
4
- class TestStreamlit:
5
- def test_main(self) -> None:
6
- assert True
@@ -1,25 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from asyncio import get_running_loop
4
- from typing import TYPE_CHECKING
5
-
6
- from aiolimiter import AsyncLimiter
7
-
8
- if TYPE_CHECKING:
9
- from collections.abc import Hashable
10
-
11
- _LIMITERS: dict[tuple[int, Hashable], AsyncLimiter] = {}
12
-
13
-
14
- def get_async_limiter(key: Hashable, /, *, rate: float = 1.0) -> AsyncLimiter:
15
- """Get a loop-aware rate limiter."""
16
- id_ = id(get_running_loop())
17
- full = (id_, key)
18
- try:
19
- return _LIMITERS[full]
20
- except KeyError:
21
- limiter = _LIMITERS[full] = AsyncLimiter(1.0, time_period=rate)
22
- return limiter
23
-
24
-
25
- __all__ = ["get_async_limiter"]
@@ -1,58 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from dataclasses import dataclass
4
- from pathlib import Path
5
- from typing import TYPE_CHECKING, override
6
-
7
- from pydantic import BaseModel
8
-
9
- from utilities.atomicwrites import writer
10
-
11
- if TYPE_CHECKING:
12
- from utilities.types import PathLike
13
-
14
-
15
- class HashableBaseModel(BaseModel):
16
- """Subclass of BaseModel which is hashable."""
17
-
18
- @override
19
- def __hash__(self) -> int:
20
- return hash((type(self), *self.__dict__.values()))
21
-
22
-
23
- def load_model[T: BaseModel](model: type[T], path: PathLike, /) -> T:
24
- path = Path(path)
25
- try:
26
- return model.model_validate_json(path.read_text())
27
- except FileNotFoundError:
28
- raise _LoadModelFileNotFoundError(model=model, path=path) from None
29
- except IsADirectoryError: # skipif-not-windows
30
- raise _LoadModelIsADirectoryError(model=model, path=path) from None
31
-
32
-
33
- @dataclass(kw_only=True, slots=True)
34
- class LoadModelError(Exception):
35
- model: type[BaseModel]
36
- path: Path
37
-
38
-
39
- @dataclass(kw_only=True, slots=True)
40
- class _LoadModelFileNotFoundError(LoadModelError):
41
- @override
42
- def __str__(self) -> str:
43
- return f"Unable to load {self.model}; path {str(self.path)!r} must exist."
44
-
45
-
46
- @dataclass(kw_only=True, slots=True)
47
- class _LoadModelIsADirectoryError(LoadModelError):
48
- @override
49
- def __str__(self) -> str:
50
- return f"Unable to load {self.model}; path {str(self.path)!r} must not be a directory." # skipif-not-windows
51
-
52
-
53
- def save_model(model: BaseModel, path: PathLike, /, *, overwrite: bool = False) -> None:
54
- with writer(path, overwrite=overwrite) as temp:
55
- _ = temp.write_text(model.model_dump_json())
56
-
57
-
58
- __all__ = ["HashableBaseModel", "LoadModelError", "load_model", "save_model"]
@@ -1,101 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from dataclasses import dataclass
4
- from os import environ
5
- from pathlib import Path
6
- from typing import TYPE_CHECKING, override
7
-
8
- from dotenv import dotenv_values
9
-
10
- from utilities.dataclasses import _ParseDataClassMissingValuesError, parse_dataclass
11
- from utilities.iterables import MergeStrMappingsError, merge_str_mappings
12
- from utilities.pathlib import get_root
13
- from utilities.reprlib import get_repr
14
- from utilities.types import Dataclass
15
-
16
- if TYPE_CHECKING:
17
- from collections.abc import Mapping
18
- from collections.abc import Set as AbstractSet
19
-
20
- from utilities.types import MaybeCallablePathLike, ParseObjectExtra, StrMapping
21
-
22
-
23
- def load_settings[T: Dataclass](
24
- cls: type[T],
25
- /,
26
- *,
27
- path: MaybeCallablePathLike | None = Path.cwd,
28
- globalns: StrMapping | None = None,
29
- localns: StrMapping | None = None,
30
- warn_name_errors: bool = False,
31
- head: bool = False,
32
- case_sensitive: bool = False,
33
- extra_parsers: ParseObjectExtra | None = None,
34
- ) -> T:
35
- """Load a set of settings from the `.env` file."""
36
- path = get_root(path=path).joinpath(".env")
37
- if not path.exists():
38
- raise _LoadSettingsFileNotFoundError(path=path) from None
39
- maybe_values_dotenv = dotenv_values(path)
40
- try:
41
- maybe_values: Mapping[str, str | None] = merge_str_mappings(
42
- maybe_values_dotenv, environ, case_sensitive=case_sensitive
43
- )
44
- except MergeStrMappingsError as error:
45
- raise _LoadSettingsDuplicateKeysError(
46
- path=path,
47
- values=error.mapping,
48
- counts=error.counts,
49
- case_sensitive=case_sensitive,
50
- ) from None
51
- values = {k: v for k, v in maybe_values.items() if v is not None}
52
- try:
53
- return parse_dataclass(
54
- values,
55
- cls,
56
- globalns=globalns,
57
- localns=localns,
58
- warn_name_errors=warn_name_errors,
59
- head=head,
60
- case_sensitive=case_sensitive,
61
- allow_extra_keys=True,
62
- extra_parsers=extra_parsers,
63
- )
64
- except _ParseDataClassMissingValuesError as error:
65
- raise _LoadSettingsMissingKeysError(path=path, fields=error.fields) from None
66
-
67
-
68
- @dataclass(kw_only=True, slots=True)
69
- class LoadSettingsError(Exception):
70
- path: Path
71
-
72
-
73
- @dataclass(kw_only=True, slots=True)
74
- class _LoadSettingsDuplicateKeysError(LoadSettingsError):
75
- values: StrMapping
76
- counts: Mapping[str, int]
77
- case_sensitive: bool = False
78
-
79
- @override
80
- def __str__(self) -> str:
81
- return f"Mapping {get_repr(dict(self.values))} keys must not contain duplicates (modulo case); got {get_repr(self.counts)}"
82
-
83
-
84
- @dataclass(kw_only=True, slots=True)
85
- class _LoadSettingsFileNotFoundError(LoadSettingsError):
86
- @override
87
- def __str__(self) -> str:
88
- return f"Path {str(self.path)!r} must exist"
89
-
90
-
91
- @dataclass(kw_only=True, slots=True)
92
- class _LoadSettingsMissingKeysError(LoadSettingsError):
93
- fields: AbstractSet[str]
94
-
95
- @override
96
- def __str__(self) -> str:
97
- desc = ", ".join(map(repr, sorted(self.fields)))
98
- return f"Unable to load {str(self.path)!r}; missing value(s) for {desc}"
99
-
100
-
101
- __all__ = ["LoadSettingsError", "load_settings"]
@@ -1,105 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from hmac import compare_digest
4
- from typing import TYPE_CHECKING, Literal
5
-
6
- from streamlit import (
7
- button,
8
- empty,
9
- error,
10
- form,
11
- form_submit_button,
12
- markdown,
13
- secrets,
14
- session_state,
15
- stop,
16
- text_input,
17
- )
18
-
19
- if TYPE_CHECKING:
20
- from collections.abc import Callable
21
-
22
- from streamlit.elements.lib.utils import Key
23
- from streamlit.runtime.state import WidgetArgs, WidgetCallback, WidgetKwargs
24
-
25
-
26
- def centered_button(
27
- label: str,
28
- /,
29
- *,
30
- key: Key | None = None,
31
- help: str | None = None, # noqa: A002
32
- on_click: WidgetCallback | None = None,
33
- args: WidgetArgs | None = None,
34
- kwargs: WidgetKwargs | None = None,
35
- type: Literal["primary", "secondary"] = "secondary", # noqa: A002
36
- disabled: bool = False,
37
- use_container_width: bool = False,
38
- ) -> bool:
39
- """Create a centered button."""
40
- style = r"<style>.row-widget.stButton {text-align: center;}</style>"
41
- _ = markdown(style, unsafe_allow_html=True)
42
- with empty():
43
- return button(
44
- label,
45
- key=key,
46
- help=help,
47
- on_click=on_click,
48
- args=args,
49
- kwargs=kwargs,
50
- type=type,
51
- disabled=disabled,
52
- use_container_width=use_container_width,
53
- )
54
-
55
-
56
- _USERNAME = "username"
57
- _PASSWORD = "password" # noqa: S105
58
- _PASSWORD_CORRECT = "password_correct" # noqa: S105
59
-
60
-
61
- def ensure_logged_in(
62
- *,
63
- skip: bool = False,
64
- before_form: Callable[..., None] | None = None,
65
- after_form: Callable[..., None] | None = None,
66
- ) -> None:
67
- """Ensure the user is logged in."""
68
- if not (skip or _check_password(before_form=before_form, after_form=after_form)):
69
- stop()
70
-
71
-
72
- def _check_password(
73
- *,
74
- before_form: Callable[..., None] | None = None,
75
- after_form: Callable[..., None] | None = None,
76
- ) -> bool:
77
- """Return `True` if the user had a correct password."""
78
- if session_state.get("password_correct", False):
79
- return True
80
- if before_form is not None:
81
- before_form()
82
- with form("Credentials"):
83
- _ = text_input("Username", key=_USERNAME)
84
- _ = text_input("Password", type="password", key=_PASSWORD)
85
- _ = form_submit_button("Log in", on_click=_password_entered)
86
- if after_form is not None:
87
- after_form()
88
- if _PASSWORD_CORRECT in session_state:
89
- _ = error("Username/password combination invalid or incorrect")
90
- return False
91
-
92
-
93
- def _password_entered() -> None:
94
- """Check whether a password entered by the user is correct."""
95
- if (session_state[_USERNAME] in secrets["passwords"]) and compare_digest(
96
- session_state[_PASSWORD], secrets.passwords[session_state[_USERNAME]]
97
- ):
98
- session_state[_PASSWORD_CORRECT] = True
99
- del session_state[_PASSWORD]
100
- del session_state[_USERNAME]
101
- else:
102
- session_state[_PASSWORD_CORRECT] = False
103
-
104
-
105
- __all__ = ["ensure_logged_in"]