dycw-utilities 0.130.1__tar.gz → 0.131.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.
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/PKG-INFO +1 -1
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/pyproject.toml +2 -2
- dycw_utilities-0.131.0/src/tests/test_pottery.py +92 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/__init__.py +1 -1
- dycw_utilities-0.131.0/src/utilities/pottery.py +116 -0
- dycw_utilities-0.130.1/src/tests/test_pottery.py +0 -28
- dycw_utilities-0.130.1/src/utilities/pottery.py +0 -50
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/.gitignore +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/LICENSE +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/README.md +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/__init__.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/conftest.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/modules/__init__.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/modules/package_missing/__init__.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/modules/package_missing/module.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/modules/package_with/__init__.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/modules/package_with/outer_1.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/modules/package_with/outer_2.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/modules/package_with/subpackage/__init__.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/modules/package_with/subpackage/inner_1.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/modules/package_with/subpackage/inner_2.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/modules/package_with/subpackage/inner_3.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/modules/package_without/__init__.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/modules/package_without/module_1.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/modules/package_without/module_2.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/modules/standalone.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/modules/with_imports.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_aiolimiter.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_altair.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_asyncio.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_asyncio_classes/__init__.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_asyncio_classes/loopers.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_asyncio_classes/redis.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_atomicwrites.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_atools.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_cachetools.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_click.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_concurrent.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_contextlib.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_contextvars.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_cryptography.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_cvxpy.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_dataclasses.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_datetime.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_enum.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_errors.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_eventkit.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_fastapi.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_fpdf2.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_functions.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_functools.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_getpass.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_git.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_hashlib.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_http.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_hypothesis.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_importlib.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_inflect.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_ipython.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_iterables.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_jupyter.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_libcst.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_lightweight_charts.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_logging.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_luigi.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_math.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_memory_profiler.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_modules.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_more_itertools.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_numpy.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_operator.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_optuna.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_orjson.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_os.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_parse.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_pathlib.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_period.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_pickle.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_platform.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_polars.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_polars_ols.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_pqdm.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_psutil.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_pydantic.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_pyinstrument.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_pyrsistent.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_pytest.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_pytest_regressions.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_python_dotenv.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_random.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_re.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_redis.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_reprlib.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_scipy.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_sentinel.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_shelve.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_slack_sdk.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_socket.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_sqlalchemy.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_sqlalchemy_polars.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_statsmodel.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_streamlit.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_string.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_tempfile.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_tenacity.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_text.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_threading.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_timer.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_traceback.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_types.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_typing.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_typing_funcs/__init__.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_typing_funcs/no_future.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_typing_funcs/with_future.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_tzdata.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_tzlocal.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_uuid.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_version.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_warnings.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_whenever.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_zipfile.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_zoneinfo.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/aiolimiter.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/altair.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/asyncio.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/atomicwrites.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/atools.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/cachetools.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/click.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/concurrent.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/contextlib.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/contextvars.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/cryptography.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/cvxpy.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/dataclasses.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/datetime.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/enum.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/errors.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/eventkit.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/fastapi.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/fpdf2.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/functions.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/functools.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/getpass.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/git.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/hashlib.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/http.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/hypothesis.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/importlib.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/inflect.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/ipython.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/iterables.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/jupyter.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/libcst.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/lightweight_charts.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/logging.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/luigi.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/math.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/memory_profiler.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/modules.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/more_itertools.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/numpy.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/operator.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/optuna.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/orjson.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/os.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/parse.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/pathlib.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/period.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/pickle.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/platform.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/polars.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/polars_ols.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/pqdm.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/psutil.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/py.typed +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/pydantic.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/pyinstrument.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/pyrsistent.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/pytest.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/pytest_regressions.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/python_dotenv.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/random.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/re.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/redis.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/reprlib.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/scipy.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/sentinel.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/shelve.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/slack_sdk.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/socket.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/sqlalchemy.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/sqlalchemy_polars.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/statsmodels.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/streamlit.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/string.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/tempfile.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/tenacity.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/text.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/threading.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/timer.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/traceback.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/types.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/typing.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/tzdata.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/tzlocal.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/uuid.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/version.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/warnings.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/whenever.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/zipfile.py +0 -0
- {dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/utilities/zoneinfo.py +0 -0
@@ -94,7 +94,7 @@ dependencies = [
|
|
94
94
|
name = "dycw-utilities"
|
95
95
|
readme = "README.md"
|
96
96
|
requires-python = ">= 3.12"
|
97
|
-
version = "0.
|
97
|
+
version = "0.131.0"
|
98
98
|
|
99
99
|
[project.optional-dependencies]
|
100
100
|
logging = [
|
@@ -341,7 +341,7 @@ zzz-test-zoneinfo = [
|
|
341
341
|
# bump-my-version
|
342
342
|
[tool.bumpversion]
|
343
343
|
allow_dirty = true
|
344
|
-
current_version = "0.
|
344
|
+
current_version = "0.131.0"
|
345
345
|
|
346
346
|
[[tool.bumpversion.files]]
|
347
347
|
filename = "src/utilities/__init__.py"
|
@@ -0,0 +1,92 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from asyncio import TaskGroup, sleep
|
4
|
+
from re import search
|
5
|
+
from typing import TYPE_CHECKING
|
6
|
+
|
7
|
+
from pytest import mark, param, raises
|
8
|
+
|
9
|
+
from tests.conftest import SKIPIF_CI_AND_NOT_LINUX
|
10
|
+
from tests.test_redis import yield_test_redis
|
11
|
+
from utilities.iterables import one
|
12
|
+
from utilities.pottery import (
|
13
|
+
_YieldAccessNumLocksError,
|
14
|
+
_YieldAccessUnableToAcquireLockError,
|
15
|
+
yield_access,
|
16
|
+
)
|
17
|
+
from utilities.text import unique_str
|
18
|
+
from utilities.timer import Timer
|
19
|
+
|
20
|
+
if TYPE_CHECKING:
|
21
|
+
from redis.asyncio import Redis
|
22
|
+
|
23
|
+
|
24
|
+
async def _func_access(num_tasks: int, key: str, /, *, num_locks: int = 1) -> None:
|
25
|
+
async def coroutine() -> None:
|
26
|
+
async with yield_test_redis() as redis, yield_access(redis, key, num=num_locks):
|
27
|
+
await sleep(0.1)
|
28
|
+
|
29
|
+
async with TaskGroup() as tg:
|
30
|
+
_ = [tg.create_task(coroutine()) for _ in range(num_tasks)]
|
31
|
+
|
32
|
+
|
33
|
+
class TestYieldAccess:
|
34
|
+
@SKIPIF_CI_AND_NOT_LINUX
|
35
|
+
@mark.parametrize(
|
36
|
+
("num_tasks", "num_locks", "min_time"),
|
37
|
+
[
|
38
|
+
param(1, 1, 0.1),
|
39
|
+
param(1, 2, 0.1),
|
40
|
+
param(1, 3, 0.1),
|
41
|
+
param(2, 1, 0.2),
|
42
|
+
param(2, 2, 0.1),
|
43
|
+
param(2, 3, 0.1),
|
44
|
+
param(2, 4, 0.1),
|
45
|
+
param(2, 5, 0.1),
|
46
|
+
param(3, 1, 0.3),
|
47
|
+
param(3, 2, 0.2),
|
48
|
+
param(3, 3, 0.1),
|
49
|
+
param(3, 4, 0.1),
|
50
|
+
param(3, 5, 0.1),
|
51
|
+
param(4, 1, 0.4),
|
52
|
+
param(4, 2, 0.2),
|
53
|
+
param(4, 3, 0.2),
|
54
|
+
param(4, 4, 0.1),
|
55
|
+
param(4, 5, 0.1),
|
56
|
+
],
|
57
|
+
)
|
58
|
+
async def test_main(
|
59
|
+
self, *, num_tasks: int, num_locks: int, min_time: float
|
60
|
+
) -> None:
|
61
|
+
with Timer() as timer:
|
62
|
+
await _func_access(num_tasks, unique_str(), num_locks=num_locks)
|
63
|
+
assert min_time <= float(timer) <= 3 * min_time
|
64
|
+
|
65
|
+
async def test_error_num_locks(self) -> None:
|
66
|
+
key = unique_str()
|
67
|
+
with raises(
|
68
|
+
_YieldAccessNumLocksError,
|
69
|
+
match=r"Number of locks for '\w+' must be positive; got 0",
|
70
|
+
):
|
71
|
+
async with yield_test_redis() as redis, yield_access(redis, key, num=0):
|
72
|
+
...
|
73
|
+
|
74
|
+
@SKIPIF_CI_AND_NOT_LINUX
|
75
|
+
async def test_error_unable_to_acquire_lock(self) -> None:
|
76
|
+
key = unique_str()
|
77
|
+
|
78
|
+
async def coroutine(redis: Redis, key: str, /) -> None:
|
79
|
+
async with yield_access(
|
80
|
+
redis, key, num=1, timeout_acquire=0.1, throttle=0.5
|
81
|
+
):
|
82
|
+
await sleep(0.1)
|
83
|
+
|
84
|
+
with raises(ExceptionGroup) as exc_info: # noqa: PT012
|
85
|
+
async with yield_test_redis() as redis, TaskGroup() as tg:
|
86
|
+
_ = tg.create_task(coroutine(redis, key))
|
87
|
+
_ = tg.create_task(coroutine(redis, key))
|
88
|
+
error = one(exc_info.value.exceptions)
|
89
|
+
assert isinstance(error, _YieldAccessUnableToAcquireLockError)
|
90
|
+
assert search(
|
91
|
+
r"Unable to acquire any 1 of 1 locks for '\w+' after 0\.1", str(error)
|
92
|
+
)
|
@@ -0,0 +1,116 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from contextlib import asynccontextmanager, suppress
|
4
|
+
from dataclasses import dataclass
|
5
|
+
from typing import TYPE_CHECKING, override
|
6
|
+
|
7
|
+
from pottery import AIORedlock
|
8
|
+
from pottery.exceptions import ReleaseUnlockedLock
|
9
|
+
from redis.asyncio import Redis
|
10
|
+
|
11
|
+
from utilities.asyncio import sleep_dur, timeout_dur
|
12
|
+
from utilities.datetime import MILLISECOND, SECOND, datetime_duration_to_float
|
13
|
+
from utilities.iterables import always_iterable
|
14
|
+
|
15
|
+
if TYPE_CHECKING:
|
16
|
+
from collections.abc import AsyncIterator, Iterable
|
17
|
+
|
18
|
+
from utilities.types import Duration, MaybeIterable
|
19
|
+
|
20
|
+
|
21
|
+
@asynccontextmanager
|
22
|
+
async def yield_access(
|
23
|
+
redis: MaybeIterable[Redis],
|
24
|
+
key: str,
|
25
|
+
/,
|
26
|
+
*,
|
27
|
+
num: int = 1,
|
28
|
+
timeout_acquire: Duration | None = None,
|
29
|
+
timeout_release: Duration = 10 * SECOND,
|
30
|
+
sleep: Duration = MILLISECOND,
|
31
|
+
throttle: Duration | None = None,
|
32
|
+
) -> AsyncIterator[None]:
|
33
|
+
"""Acquire access to a locked resource, amongst 1 of multiple connections."""
|
34
|
+
if num <= 0:
|
35
|
+
raise _YieldAccessNumLocksError(key=key, num=num)
|
36
|
+
masters = ( # skipif-ci-and-not-linux
|
37
|
+
{redis} if isinstance(redis, Redis) else set(always_iterable(redis))
|
38
|
+
)
|
39
|
+
auto_release_time = datetime_duration_to_float( # skipif-ci-and-not-linux
|
40
|
+
timeout_release
|
41
|
+
)
|
42
|
+
locks = [ # skipif-ci-and-not-linux
|
43
|
+
AIORedlock(
|
44
|
+
key=f"{key}_{i}_of_{num}",
|
45
|
+
masters=masters,
|
46
|
+
auto_release_time=auto_release_time,
|
47
|
+
)
|
48
|
+
for i in range(1, num + 1)
|
49
|
+
]
|
50
|
+
lock: AIORedlock | None = None # skipif-ci-and-not-linux
|
51
|
+
try: # skipif-ci-and-not-linux
|
52
|
+
lock = await _get_first_available_lock(
|
53
|
+
key, locks, num=num, timeout=timeout_acquire, sleep=sleep
|
54
|
+
)
|
55
|
+
yield
|
56
|
+
finally: # skipif-ci-and-not-linux
|
57
|
+
await sleep_dur(duration=throttle)
|
58
|
+
if lock is not None:
|
59
|
+
with suppress(ReleaseUnlockedLock):
|
60
|
+
await lock.release()
|
61
|
+
|
62
|
+
|
63
|
+
async def _get_first_available_lock(
|
64
|
+
key: str,
|
65
|
+
locks: Iterable[AIORedlock],
|
66
|
+
/,
|
67
|
+
*,
|
68
|
+
num: int = 1,
|
69
|
+
timeout: Duration | None = None,
|
70
|
+
sleep: Duration | None = None,
|
71
|
+
) -> AIORedlock:
|
72
|
+
locks = list(locks) # skipif-ci-and-not-linux
|
73
|
+
error = _YieldAccessUnableToAcquireLockError( # skipif-ci-and-not-linux
|
74
|
+
key=key, num=num, timeout=timeout
|
75
|
+
)
|
76
|
+
async with timeout_dur( # skipif-ci-and-not-linux
|
77
|
+
duration=timeout, error=error
|
78
|
+
):
|
79
|
+
while True:
|
80
|
+
if (result := await _get_first_available_lock_if_any(locks)) is not None:
|
81
|
+
return result
|
82
|
+
await sleep_dur(duration=sleep)
|
83
|
+
|
84
|
+
|
85
|
+
async def _get_first_available_lock_if_any(
|
86
|
+
locks: Iterable[AIORedlock], /
|
87
|
+
) -> AIORedlock | None:
|
88
|
+
for lock in locks: # skipif-ci-and-not-linux
|
89
|
+
if await lock.acquire(blocking=False):
|
90
|
+
return lock
|
91
|
+
return None # skipif-ci-and-not-linux
|
92
|
+
|
93
|
+
|
94
|
+
@dataclass(kw_only=True, slots=True)
|
95
|
+
class YieldAccessError(Exception):
|
96
|
+
key: str
|
97
|
+
num: int
|
98
|
+
|
99
|
+
|
100
|
+
@dataclass(kw_only=True, slots=True)
|
101
|
+
class _YieldAccessNumLocksError(YieldAccessError):
|
102
|
+
@override
|
103
|
+
def __str__(self) -> str:
|
104
|
+
return f"Number of locks for {self.key!r} must be positive; got {self.num}"
|
105
|
+
|
106
|
+
|
107
|
+
@dataclass(kw_only=True, slots=True)
|
108
|
+
class _YieldAccessUnableToAcquireLockError(YieldAccessError):
|
109
|
+
timeout: Duration | None
|
110
|
+
|
111
|
+
@override
|
112
|
+
def __str__(self) -> str:
|
113
|
+
return f"Unable to acquire any 1 of {self.num} locks for {self.key!r} after {self.timeout}" # skipif-ci-and-not-linux
|
114
|
+
|
115
|
+
|
116
|
+
__all__ = ["YieldAccessError", "yield_access"]
|
@@ -1,28 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
from asyncio import TaskGroup, sleep
|
4
|
-
|
5
|
-
from tests.conftest import SKIPIF_CI_AND_NOT_LINUX
|
6
|
-
from tests.test_redis import yield_test_redis
|
7
|
-
from utilities.pottery import yield_locked_resource
|
8
|
-
from utilities.text import unique_str
|
9
|
-
from utilities.timer import Timer
|
10
|
-
|
11
|
-
|
12
|
-
async def _coroutine(key: str) -> None:
|
13
|
-
async with yield_test_redis() as redis, yield_locked_resource(redis, key):
|
14
|
-
await sleep(0.1)
|
15
|
-
|
16
|
-
|
17
|
-
async def _asyncio_runner(num_tasks: int, key: str) -> None:
|
18
|
-
async with TaskGroup() as tg:
|
19
|
-
_ = [tg.create_task(_coroutine(key)) for _ in range(num_tasks)]
|
20
|
-
|
21
|
-
|
22
|
-
class TestYieldLockedResource:
|
23
|
-
@SKIPIF_CI_AND_NOT_LINUX
|
24
|
-
async def test_single_process(self) -> None:
|
25
|
-
with Timer() as timer:
|
26
|
-
await _asyncio_runner(3, unique_str())
|
27
|
-
min_time = 0.3
|
28
|
-
assert min_time <= float(timer) <= 3 * min_time
|
@@ -1,50 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
import asyncio
|
4
|
-
from contextlib import asynccontextmanager, suppress
|
5
|
-
from typing import TYPE_CHECKING
|
6
|
-
|
7
|
-
from pottery import AIORedlock
|
8
|
-
from pottery.exceptions import ReleaseUnlockedLock
|
9
|
-
from redis.asyncio import Redis
|
10
|
-
|
11
|
-
from utilities.datetime import MILLISECOND, SECOND, datetime_duration_to_float
|
12
|
-
from utilities.iterables import always_iterable
|
13
|
-
|
14
|
-
if TYPE_CHECKING:
|
15
|
-
from collections.abc import AsyncIterator
|
16
|
-
|
17
|
-
from utilities.types import Duration, MaybeIterable
|
18
|
-
|
19
|
-
|
20
|
-
@asynccontextmanager
|
21
|
-
async def yield_locked_resource(
|
22
|
-
redis: MaybeIterable[Redis],
|
23
|
-
key: str,
|
24
|
-
/,
|
25
|
-
*,
|
26
|
-
duration: Duration = 10 * SECOND,
|
27
|
-
sleep: Duration = MILLISECOND,
|
28
|
-
) -> AsyncIterator[None]:
|
29
|
-
"""Yield a locked resource."""
|
30
|
-
masters = ( # skipif-ci-and-not-linux
|
31
|
-
{redis} if isinstance(redis, Redis) else set(always_iterable(redis))
|
32
|
-
)
|
33
|
-
duration_use = datetime_duration_to_float(duration) # skipif-ci-and-not-linux
|
34
|
-
lock = AIORedlock( # skipif-ci-and-not-linux
|
35
|
-
key=key,
|
36
|
-
masters=masters,
|
37
|
-
auto_release_time=duration_use,
|
38
|
-
context_manager_timeout=duration_use,
|
39
|
-
)
|
40
|
-
sleep_use = datetime_duration_to_float(sleep) # skipif-ci-and-not-linux
|
41
|
-
while not await lock.acquire(): # pragma: no cover
|
42
|
-
_ = await asyncio.sleep(sleep_use)
|
43
|
-
try: # skipif-ci-and-not-linux
|
44
|
-
yield
|
45
|
-
finally: # skipif-ci-and-not-linux
|
46
|
-
with suppress(ReleaseUnlockedLock):
|
47
|
-
await lock.release()
|
48
|
-
|
49
|
-
|
50
|
-
__all__ = ["yield_locked_resource"]
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/modules/package_missing/__init__.py
RENAMED
File without changes
|
{dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/modules/package_missing/module.py
RENAMED
File without changes
|
{dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/modules/package_with/__init__.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/modules/package_without/__init__.py
RENAMED
File without changes
|
{dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/modules/package_without/module_1.py
RENAMED
File without changes
|
{dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/modules/package_without/module_2.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_asyncio_classes/__init__.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{dycw_utilities-0.130.1 → dycw_utilities-0.131.0}/src/tests/test_typing_funcs/with_future.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|