dycw-utilities 0.132.1__tar.gz → 0.132.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/PKG-INFO +1 -2
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/pyproject.toml +81 -71
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/conftest.py +2 -1
- dycw_utilities-0.132.3/src/tests/test_typed_settings.py +147 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/__init__.py +1 -1
- dycw_utilities-0.132.3/src/utilities/typed_settings.py +124 -0
- dycw_utilities-0.132.1/src/tests/test_pyrsistent.py +0 -49
- dycw_utilities-0.132.1/src/tests/test_typed_settings.py +0 -99
- dycw_utilities-0.132.1/src/utilities/pyrsistent.py +0 -89
- dycw_utilities-0.132.1/src/utilities/typed_settings.py +0 -61
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/.gitignore +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/LICENSE +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/README.md +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/__init__.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/modules/__init__.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/modules/package_missing/__init__.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/modules/package_missing/module.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/modules/package_with/__init__.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/modules/package_with/outer_1.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/modules/package_with/outer_2.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/modules/package_with/subpackage/__init__.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/modules/package_with/subpackage/inner_1.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/modules/package_with/subpackage/inner_2.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/modules/package_with/subpackage/inner_3.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/modules/package_without/__init__.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/modules/package_without/module_1.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/modules/package_without/module_2.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/modules/standalone.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/modules/with_imports.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_aiolimiter.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_altair.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_asyncio.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_asyncio_classes/__init__.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_asyncio_classes/loopers.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_asyncio_classes/redis.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_atomicwrites.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_atools.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_cachetools.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_click.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_concurrent.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_contextlib.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_contextvars.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_cryptography.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_cvxpy.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_dataclasses.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_enum.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_errors.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_eventkit.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_fastapi.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_fpdf2.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_functions.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_functools.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_getpass.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_git.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_hashlib.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_http.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_hypothesis.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_importlib.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_inflect.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_ipython.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_iterables.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_jupyter.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_libcst.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_lightweight_charts.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_logging.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_luigi.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_math.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_memory_profiler.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_modules.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_more_itertools.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_numpy.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_objects/__init__.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_objects/objects.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_operator.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_optuna.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_orjson.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_os.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_parse.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_pathlib.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_period.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_pickle.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_platform.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_polars.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_polars_ols.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_pottery.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_pqdm.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_psutil.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_pydantic.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_pyinstrument.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_pytest.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_pytest_regressions.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_python_dotenv.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_random.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_re.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_redis.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_reprlib.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_scipy.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_sentinel.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_shelve.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_slack_sdk.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_socket.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_sqlalchemy.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_sqlalchemy_polars.py +0 -0
- /dycw_utilities-0.132.1/src/tests/test_statsmodel.py → /dycw_utilities-0.132.3/src/tests/test_statsmodels.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_streamlit.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_string.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_tempfile.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_text.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_threading.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_timer.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_traceback.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_types.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_typing.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_typing_funcs/__init__.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_typing_funcs/no_future.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_typing_funcs/with_future.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_tzdata.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_tzlocal.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_uuid.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_version.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_warnings.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_whenever.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_zipfile.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/tests/test_zoneinfo.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/aiolimiter.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/altair.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/asyncio.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/atomicwrites.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/atools.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/cachetools.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/click.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/concurrent.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/contextlib.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/contextvars.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/cryptography.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/cvxpy.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/dataclasses.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/enum.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/errors.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/eventkit.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/fastapi.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/fpdf2.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/functions.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/functools.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/getpass.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/git.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/hashlib.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/http.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/hypothesis.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/importlib.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/inflect.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/ipython.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/iterables.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/jupyter.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/libcst.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/lightweight_charts.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/logging.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/luigi.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/math.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/memory_profiler.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/modules.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/more_itertools.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/numpy.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/operator.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/optuna.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/orjson.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/os.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/parse.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/pathlib.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/period.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/pickle.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/platform.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/polars.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/polars_ols.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/pottery.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/pqdm.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/psutil.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/py.typed +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/pydantic.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/pyinstrument.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/pytest.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/pytest_regressions.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/python_dotenv.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/random.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/re.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/redis.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/reprlib.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/scipy.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/sentinel.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/shelve.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/slack_sdk.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/socket.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/sqlalchemy.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/sqlalchemy_polars.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/statsmodels.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/streamlit.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/string.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/tempfile.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/text.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/threading.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/timer.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/traceback.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/types.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/typing.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/tzdata.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/tzlocal.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/uuid.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/version.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/warnings.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/whenever.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/zipfile.py +0 -0
- {dycw_utilities-0.132.1 → dycw_utilities-0.132.3}/src/utilities/zoneinfo.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: dycw-utilities
|
3
|
-
Version: 0.132.
|
3
|
+
Version: 0.132.3
|
4
4
|
Author-email: Derek Wan <d.wan@icloud.com>
|
5
5
|
License-File: LICENSE
|
6
6
|
Requires-Python: >=3.12
|
@@ -17,7 +17,6 @@ Requires-Dist: pytest-asyncio<1.1,>=1.0.0; extra == 'test'
|
|
17
17
|
Requires-Dist: pytest-cov<6.2,>=6.1.1; extra == 'test'
|
18
18
|
Requires-Dist: pytest-instafail<0.6,>=0.5.0; extra == 'test'
|
19
19
|
Requires-Dist: pytest-lazy-fixtures<1.2,>=1.1.4; extra == 'test'
|
20
|
-
Requires-Dist: pytest-randomly<3.17,>=3.16.0; extra == 'test'
|
21
20
|
Requires-Dist: pytest-regressions<2.9,>=2.8.0; extra == 'test'
|
22
21
|
Requires-Dist: pytest-rerunfailures<16,>=15.1; extra == 'test'
|
23
22
|
Requires-Dist: pytest-rng<1.1,>=1.0.0; extra == 'test'
|
@@ -7,80 +7,88 @@ requires = ["hatchling"]
|
|
7
7
|
|
8
8
|
# dependency groups
|
9
9
|
[dependency-groups]
|
10
|
+
aiolimiter = ["aiolimiter >= 1.2.1, < 1.3"]
|
11
|
+
altair = ["altair >= 5.5.0, < 5.6"]
|
12
|
+
altair-test = [
|
13
|
+
"polars-lts-cpu",
|
14
|
+
"img2pdf",
|
15
|
+
"vl-convert-python",
|
16
|
+
]
|
17
|
+
atools = ["atools >= 0.14.2, < 0.15"]
|
18
|
+
cachetools = ["cachetools >= 5.5.2, < 5.6"]
|
19
|
+
click = ["click >= 8.2.1, < 8.3"]
|
20
|
+
core = [
|
21
|
+
"atomicwrites >= 1.4.1, < 1.5",
|
22
|
+
"typing-extensions >= 4.14.0, < 4.15",
|
23
|
+
"tzlocal >= 5.3.1, < 5.4",
|
24
|
+
"whenever >= 0.8.5, < 0.9",
|
25
|
+
]
|
26
|
+
cryptography = ["cryptography >= 45.0.4, < 45.1"]
|
27
|
+
cvxpy = ["cvxpy >= 1.6.5, < 1.7"]
|
28
|
+
dataclasses-test = ["orjson", "polars-lts-cpu"]
|
10
29
|
dev = [
|
11
|
-
"aiohttp >= 3.12.12, < 3.13", # for slack
|
12
|
-
"aiolimiter >= 1.2.1, < 1.3",
|
13
|
-
"aiosqlite >= 0.21.0, < 0.22",
|
14
|
-
"altair >= 5.5.0, < 5.6",
|
15
|
-
"asyncpg >= 0.30.0, < 0.31", # for sqlalchemy async
|
16
|
-
"atools >= 0.14.2, < 0.15",
|
17
|
-
"cachetools >= 5.5.2, < 5.6", # blocked by streamlit
|
18
|
-
"click >= 8.2.1, < 8.3",
|
19
30
|
"coloredlogs >= 15.0.1, < 15.1",
|
20
|
-
"
|
21
|
-
"cvxpy >= 1.6.5, < 1.7",
|
22
|
-
"eventkit >= 1.0.3, < 1.1",
|
23
|
-
"fastapi >= 0.115.11, < 0.116",
|
24
|
-
"fpdf2 >= 2.8.3, < 2.9",
|
25
|
-
"greenlet >= 3.2.0, < 3.3", # for sqlalchemy async
|
26
|
-
"httpx >= 0.28.1, < 0.29", # for fastapi
|
27
|
-
"hypothesis >= 6.135.2, < 6.136",
|
28
|
-
"img2pdf >= 0.6.0, < 0.7",
|
29
|
-
"inflect >= 7.5.0, < 7.6",
|
30
|
-
"lightweight-charts >= 2.1, < 2.2",
|
31
|
-
"luigi >= 3.6.0, < 3.7",
|
32
|
-
"memory-profiler >= 0.61.0, < 0.62",
|
33
|
-
"more-itertools >= 10.7.0, < 10.8",
|
34
|
-
"nest-asyncio >= 1.6.0, < 1.7", # for sqlalchemy async
|
35
|
-
"nox >= 2025.2.9, < 2025.3",
|
36
|
-
"numpy >= 2.3.0, < 2.4",
|
37
|
-
"libcst >= 1.8.0, < 1.9",
|
38
|
-
"optuna >= 4.3.0, < 4.4",
|
39
|
-
"orjson >= 3.10.18, < 3.11",
|
40
|
-
"pathvalidate >= 3.2.3, < 3.3",
|
41
|
-
"polars-lts-cpu >= 1.30.0, < 1.31",
|
42
|
-
"polars-ols >= 0.3.5, < 0.4",
|
43
|
-
"pottery >= 3.0.1, < 3.1",
|
44
|
-
"pqdm >= 0.2.0, < 0.3",
|
45
|
-
"psutil >= 7.0.0, < 7.1",
|
46
|
-
"psycopg2-binary >= 2.9.10, < 2.10", # for sqlalchemy
|
47
|
-
"pydantic >= 2.11.4, < 2.12",
|
48
|
-
"pyinstrument >= 5.0.2, < 5.1",
|
31
|
+
"coverage-conditional-plugin >= 0.9.0, < 0.10",
|
49
32
|
"pyright[nodejs] >= 1.1.401, < 1.2",
|
50
|
-
"
|
33
|
+
"pytest-cov >= 6.1.1, < 6.2",
|
34
|
+
]
|
35
|
+
eventkit = ["eventkit >= 1.0.3, < 1.1"]
|
36
|
+
fastapi = ["fastapi >= 0.115.11, < 0.116"]
|
37
|
+
fastapi-test = ["httpx", "uvicorn"]
|
38
|
+
fpdf2 = ["fpdf2 >= 2.8.3, < 2.9"]
|
39
|
+
hashlib-test = ["orjson"]
|
40
|
+
http-test = ["orjson"]
|
41
|
+
hypothesis = ["hypothesis >= 6.135.2, < 6.136"]
|
42
|
+
hypothesis-test = ["luigi", "pathvalidate", "numpy", "pytest-rerunfailures"]
|
43
|
+
inflect = ["inflect >= 7.5.0, < 7.6"]
|
44
|
+
jupyter-test = ["pandas", "polars"]
|
45
|
+
libcst = ["libcst >= 1.8.0, < 1.9"]
|
46
|
+
lightweight-charts = ["lightweight-charts >= 2.1, < 2.2"]
|
47
|
+
lightweight-charts-test = ["polars-lts-cpu", "pyarrow"]
|
48
|
+
luigi = ["luigi >= 3.6.0, < 3.7"]
|
49
|
+
math-test = ["numpy"]
|
50
|
+
memory-profiler = ["memory-profiler >= 0.61.0, < 0.62"]
|
51
|
+
more-itertools = ["more-itertools >= 10.7.0, < 10.8"]
|
52
|
+
numpy = ["numpy >= 2.3.0, < 2.4"]
|
53
|
+
operator = ["polars-lts-cpu"]
|
54
|
+
optuna = ["optuna >= 4.3.0, < 4.4"]
|
55
|
+
orjson = ["orjson >= 3.10.18, < 3.11"]
|
56
|
+
orjson-test = ["polars-lts-cpu"]
|
57
|
+
polars = ["polars-lts-cpu >= 1.30.0, < 1.31"]
|
58
|
+
polars-ols = ["polars-ols >= 0.3.5, < 0.4"]
|
59
|
+
polars-ols-test = ["scikit-learn"]
|
60
|
+
polars-test = ["numpy", "statsmodels"]
|
61
|
+
pottery = ["pottery >= 3.0.1, < 3.1"]
|
62
|
+
pottery-test = ["orjson", "pytest-rerunfailures"]
|
63
|
+
pqdm = ["pqdm >= 0.2.0, < 0.3"]
|
64
|
+
psutil = ["psutil >= 7.0.0, < 7.1"]
|
65
|
+
pydantic = ["pydantic >= 2.11.4, < 2.12"]
|
66
|
+
pyinstrument = ["pyinstrument >= 5.0.2, < 5.1"]
|
67
|
+
pytest = [
|
51
68
|
"pytest >= 8.3.5, < 8.4",
|
52
|
-
"pytest-regressions >= 2.8.0, < 2.9",
|
53
|
-
"python-dotenv >= 1.1.0, < 1.2",
|
54
|
-
"redis >= 6.2.0, < 6.3",
|
55
|
-
"rich >= 14.0.0, < 14.1",
|
56
|
-
"scikit-learn >= 1.7.0, < 1.8",
|
57
|
-
"scipy >= 1.15.3, < 1.16",
|
58
|
-
"slack-sdk >= 3.35.0, < 3.36",
|
59
|
-
"sqlalchemy >= 2.0.41, < 2.1",
|
60
|
-
"statsmodels >= 0.14.4, < 0.15",
|
61
|
-
"streamlit >= 1.45.0, < 1.46",
|
62
|
-
"tomlkit >= 0.13.2, < 0.14",
|
63
|
-
"typed-settings >= 24.6.0, < 24.7",
|
64
|
-
"tzdata >= 2025.2, < 2025.3",
|
65
|
-
"uvicorn >= 0.34.1, < 0.35",
|
66
|
-
"vegafusion >= 2.0.2, < 2.1",
|
67
|
-
"vegafusion-python-embed >= 1.6.9, < 1.7",
|
68
|
-
"vl-convert-python >= 1.8.0, < 1.9",
|
69
|
-
# test
|
70
|
-
"coverage-conditional-plugin >= 0.9.0, < 0.10",
|
71
|
-
"dycw-pytest-only >= 2.1.1, < 2.2",
|
72
69
|
"pytest-asyncio >= 1.0.0, < 1.1",
|
73
|
-
"pytest-cov >= 6.1.1, < 6.2",
|
74
|
-
"pytest-instafail >= 0.5.0, < 0.6",
|
75
|
-
"pytest-lazy-fixtures >= 1.1.4, < 1.2",
|
76
70
|
"pytest-randomly >= 3.16.0, < 3.17",
|
77
|
-
"pytest-rerunfailures >= 15.1, < 16",
|
78
|
-
"pytest-rng >= 1.0.0, < 1.1",
|
79
|
-
"pytest-timeout >= 2.4.0, < 2.5",
|
80
71
|
"pytest-xdist >= 3.7.0, < 3.8",
|
81
|
-
# CI
|
82
|
-
"aiolimiter>=1.2.1",
|
83
72
|
]
|
73
|
+
pytest-regressions = ["pytest-regressions >= 2.8.0, < 2.9"]
|
74
|
+
pytest-regressions-test = ["orjson", "polars-lts-cpu"]
|
75
|
+
pytest-test = ["orjson", "pytest-rng", "pytest-rerunfailures"]
|
76
|
+
python-dotenv = ["python-dotenv >= 1.1.0, < 1.2"]
|
77
|
+
redis = ["redis >= 6.2.0, < 6.3", "orjson"]
|
78
|
+
redis-test = ["pytest-rerunfailures"]
|
79
|
+
reprlib-test = ["rich"]
|
80
|
+
scipy = ["scipy >= 1.15.3, < 1.16"]
|
81
|
+
sklearn = ["scikit-learn >= 1.7.0, < 1.8"]
|
82
|
+
slack-sdk = ["slack-sdk >= 3.35.0, < 3.36"]
|
83
|
+
slack-sdk-test = ["aiohttp"]
|
84
|
+
sqlalchemy = ["sqlalchemy >= 2.0.41, < 2.1"]
|
85
|
+
sqlalchemy-polars = ["sqlalchemy", "polars-lts-cpu"]
|
86
|
+
sqlalchemy-polars-test = ["aiosqlite", "asyncpg", "greenlet"]
|
87
|
+
sqlalchemy-test = ["aiosqlite", "asyncpg", "greenlet"]
|
88
|
+
statsmodels = ["statsmodels >= 0.14.4, < 0.15"]
|
89
|
+
streamlit = ["streamlit >= 1.45.0, < 1.46"]
|
90
|
+
typed-settings = ["typed-settings >= 24.6.0, < 24.7"]
|
91
|
+
tzdata = ["tzdata >= 2025.2, < 2025.3"]
|
84
92
|
|
85
93
|
# project
|
86
94
|
[project]
|
@@ -94,7 +102,7 @@ dependencies = [
|
|
94
102
|
name = "dycw-utilities"
|
95
103
|
readme = "README.md"
|
96
104
|
requires-python = ">= 3.12"
|
97
|
-
version = "0.132.
|
105
|
+
version = "0.132.3"
|
98
106
|
|
99
107
|
[project.optional-dependencies]
|
100
108
|
logging = [
|
@@ -108,7 +116,6 @@ test = [
|
|
108
116
|
"pytest-cov >= 6.1.1, < 6.2",
|
109
117
|
"pytest-instafail >= 0.5.0, < 0.6",
|
110
118
|
"pytest-lazy-fixtures >= 1.1.4, < 1.2",
|
111
|
-
"pytest-randomly >= 3.16.0, < 3.17",
|
112
119
|
"pytest-regressions >= 2.8.0, < 2.9",
|
113
120
|
"pytest-rerunfailures >= 15.1, < 16",
|
114
121
|
"pytest-rng >= 1.0.0, < 1.1",
|
@@ -121,7 +128,7 @@ test = [
|
|
121
128
|
# bump-my-version
|
122
129
|
[tool.bumpversion]
|
123
130
|
allow_dirty = true
|
124
|
-
current_version = "0.132.
|
131
|
+
current_version = "0.132.3"
|
125
132
|
|
126
133
|
[[tool.bumpversion.files]]
|
127
134
|
filename = "src/utilities/__init__.py"
|
@@ -223,7 +230,6 @@ addopts = [
|
|
223
230
|
"--durations=10",
|
224
231
|
"--durations-min=10",
|
225
232
|
"--strict-markers",
|
226
|
-
"--timeout=300",
|
227
233
|
]
|
228
234
|
asyncio_default_fixture_loop_scope = "function"
|
229
235
|
asyncio_mode = "auto"
|
@@ -233,7 +239,7 @@ filterwarnings = [
|
|
233
239
|
"ignore:Implicitly cleaning up <TemporaryDirectory '.*'>:ResourceWarning",
|
234
240
|
"ignore:ResourceTracker called reentrantly for resource cleanup, which is unsupported:UserWarning",
|
235
241
|
"ignore:Task .* without outputs has no custom complete.* method:UserWarning", # luigi
|
236
|
-
"ignore:The garbage collector is trying to clean up non-checked-in connection <AdaptedConnection <Connection(.*)
|
242
|
+
"ignore:The garbage collector is trying to clean up non-checked-in connection <AdaptedConnection <Connection(.*)>:RuntimeWarning", # sqlalchemy
|
237
243
|
"ignore:There is no current event loop:DeprecationWarning", # eventkit
|
238
244
|
"ignore:Using fork.* can cause Polars to deadlock in the child process:RuntimeWarning", # polars/pqdm
|
239
245
|
"ignore:coroutine 'AsyncConnection.close' was never awaited:RuntimeWarning",
|
@@ -328,3 +334,7 @@ ban-relative-imports = "all"
|
|
328
334
|
[tool.ruff.lint.isort]
|
329
335
|
required-imports = ["from __future__ import annotations"]
|
330
336
|
split-on-trailing-comma = false
|
337
|
+
|
338
|
+
# uv
|
339
|
+
[tool.uv]
|
340
|
+
default-groups = "all"
|
@@ -8,7 +8,6 @@ from typing import TYPE_CHECKING, Any
|
|
8
8
|
|
9
9
|
from hypothesis import HealthCheck
|
10
10
|
from pytest import fixture, mark, param
|
11
|
-
from sqlalchemy import text
|
12
11
|
from whenever import PlainDateTime
|
13
12
|
|
14
13
|
from utilities.platform import IS_NOT_LINUX, IS_WINDOWS
|
@@ -64,6 +63,8 @@ def set_log_factory() -> AbstractContextManager[None]:
|
|
64
63
|
|
65
64
|
@fixture(params=[param("sqlite"), param("postgresql", marks=SKIPIF_CI)])
|
66
65
|
async def test_engine(*, request: SubRequest, tmp_path: Path) -> Any:
|
66
|
+
from sqlalchemy import text
|
67
|
+
|
67
68
|
from utilities.sqlalchemy import create_async_engine
|
68
69
|
|
69
70
|
dialect = request.param
|
@@ -0,0 +1,147 @@
|
|
1
|
+
from collections.abc import Callable
|
2
|
+
from dataclasses import dataclass
|
3
|
+
from ipaddress import IPv4Address, IPv6Address
|
4
|
+
from pathlib import Path
|
5
|
+
from typing import TypeVar
|
6
|
+
|
7
|
+
import typed_settings
|
8
|
+
from hypothesis import given
|
9
|
+
from hypothesis.strategies import DataObject, SearchStrategy, data, ip_addresses, tuples
|
10
|
+
from pytest import mark, param, raises
|
11
|
+
from typed_settings import EnvLoader, FileLoader, TomlFormat
|
12
|
+
from whenever import (
|
13
|
+
Date,
|
14
|
+
DateDelta,
|
15
|
+
DateTimeDelta,
|
16
|
+
PlainDateTime,
|
17
|
+
Time,
|
18
|
+
TimeDelta,
|
19
|
+
ZonedDateTime,
|
20
|
+
)
|
21
|
+
|
22
|
+
from utilities.hypothesis import (
|
23
|
+
date_deltas,
|
24
|
+
date_time_deltas,
|
25
|
+
dates,
|
26
|
+
plain_datetimes,
|
27
|
+
temp_paths,
|
28
|
+
text_ascii,
|
29
|
+
time_deltas,
|
30
|
+
times,
|
31
|
+
zoned_datetimes,
|
32
|
+
)
|
33
|
+
from utilities.os import temp_environ
|
34
|
+
from utilities.text import strip_and_dedent
|
35
|
+
from utilities.typed_settings import (
|
36
|
+
ExtendedTSConverter,
|
37
|
+
LoadSettingsError,
|
38
|
+
load_settings,
|
39
|
+
)
|
40
|
+
|
41
|
+
app_names = text_ascii(min_size=1).map(str.lower)
|
42
|
+
|
43
|
+
|
44
|
+
_T = TypeVar("_T")
|
45
|
+
|
46
|
+
|
47
|
+
class TestExtendedTSConverter:
|
48
|
+
@given(data=data(), root=temp_paths(), app_name=app_names)
|
49
|
+
@mark.parametrize(
|
50
|
+
("test_cls", "strategy", "serialize"),
|
51
|
+
[
|
52
|
+
param(Date, dates(), Date.format_common_iso),
|
53
|
+
param(DateDelta, date_deltas(parsable=True), DateDelta.format_common_iso),
|
54
|
+
param(
|
55
|
+
DateTimeDelta,
|
56
|
+
date_time_deltas(parsable=True),
|
57
|
+
DateTimeDelta.format_common_iso,
|
58
|
+
),
|
59
|
+
param(IPv4Address, ip_addresses(v=4), IPv4Address),
|
60
|
+
param(IPv6Address, ip_addresses(v=6), IPv6Address),
|
61
|
+
param(PlainDateTime, plain_datetimes(), PlainDateTime.format_common_iso),
|
62
|
+
param(Time, times(), Time.format_common_iso),
|
63
|
+
param(TimeDelta, time_deltas(), TimeDelta.format_common_iso),
|
64
|
+
param(ZonedDateTime, zoned_datetimes(), ZonedDateTime.format_common_iso),
|
65
|
+
],
|
66
|
+
)
|
67
|
+
def test_main(
|
68
|
+
self,
|
69
|
+
*,
|
70
|
+
data: DataObject,
|
71
|
+
root: Path,
|
72
|
+
app_name: str,
|
73
|
+
test_cls: type[_T],
|
74
|
+
strategy: SearchStrategy[_T],
|
75
|
+
serialize: Callable[[_T], str],
|
76
|
+
) -> None:
|
77
|
+
default, value = data.draw(tuples(strategy, strategy))
|
78
|
+
|
79
|
+
@dataclass(frozen=True, kw_only=True, slots=True)
|
80
|
+
class Settings:
|
81
|
+
value: test_cls = default # pyright: ignore[reportInvalidTypeForm]
|
82
|
+
|
83
|
+
settings_default = typed_settings.load_settings(
|
84
|
+
Settings, loaders=[], converter=ExtendedTSConverter()
|
85
|
+
)
|
86
|
+
assert settings_default.value == default
|
87
|
+
file = Path(root, "file.toml")
|
88
|
+
_ = file.write_text(
|
89
|
+
strip_and_dedent(f"""
|
90
|
+
[{app_name}]
|
91
|
+
value = '{serialize(value)}'
|
92
|
+
""")
|
93
|
+
)
|
94
|
+
settings_loaded = typed_settings.load_settings(
|
95
|
+
Settings,
|
96
|
+
loaders=[
|
97
|
+
FileLoader(formats={"*.toml": TomlFormat(app_name)}, files=[file])
|
98
|
+
],
|
99
|
+
converter=ExtendedTSConverter(),
|
100
|
+
)
|
101
|
+
assert settings_loaded.value == value
|
102
|
+
|
103
|
+
|
104
|
+
class TestLoadSettings:
|
105
|
+
@given(root=temp_paths(), datetime=zoned_datetimes())
|
106
|
+
def test_main(self, *, root: Path, datetime: ZonedDateTime) -> None:
|
107
|
+
@dataclass(frozen=True, kw_only=True, slots=True)
|
108
|
+
class Settings:
|
109
|
+
datetime: ZonedDateTime
|
110
|
+
|
111
|
+
file = Path(root, "file.toml")
|
112
|
+
_ = file.write_text("")
|
113
|
+
_ = file.write_text(
|
114
|
+
strip_and_dedent(f"""
|
115
|
+
[app_name]
|
116
|
+
datetime = '{datetime.format_common_iso()}'
|
117
|
+
""")
|
118
|
+
)
|
119
|
+
settings = load_settings(
|
120
|
+
Settings, "app_name", filenames="file.toml", start_dir=root
|
121
|
+
)
|
122
|
+
assert settings.datetime == datetime
|
123
|
+
|
124
|
+
@given(
|
125
|
+
prefix=app_names.map(lambda text: f"TEST_{text}".upper()),
|
126
|
+
datetime=zoned_datetimes(),
|
127
|
+
)
|
128
|
+
def test_loaders(self, *, prefix: str, datetime: ZonedDateTime) -> None:
|
129
|
+
key = f"{prefix}__DATETIME"
|
130
|
+
|
131
|
+
@dataclass(frozen=True, kw_only=True, slots=True)
|
132
|
+
class Settings:
|
133
|
+
datetime: ZonedDateTime
|
134
|
+
|
135
|
+
with temp_environ({key: datetime.format_common_iso()}):
|
136
|
+
settings = load_settings(
|
137
|
+
Settings, "app_name", loaders=[EnvLoader(prefix=f"{prefix}__")]
|
138
|
+
)
|
139
|
+
assert settings.datetime == datetime
|
140
|
+
|
141
|
+
@mark.parametrize("app_name", [param("app_"), param("app1"), param("app__name")])
|
142
|
+
def test_error(self, *, app_name: str) -> None:
|
143
|
+
@dataclass(frozen=True, kw_only=True, slots=True)
|
144
|
+
class Settings: ...
|
145
|
+
|
146
|
+
with raises(LoadSettingsError, match="Invalid app name; got '.+'"):
|
147
|
+
_ = load_settings(Settings, app_name)
|
@@ -0,0 +1,124 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from dataclasses import dataclass
|
4
|
+
from ipaddress import IPv4Address, IPv6Address
|
5
|
+
from pathlib import Path
|
6
|
+
from re import search
|
7
|
+
from typing import TYPE_CHECKING, Any, TypeVar, override
|
8
|
+
|
9
|
+
import typed_settings
|
10
|
+
from typed_settings import EnvLoader, FileLoader, find
|
11
|
+
from typed_settings.converters import TSConverter
|
12
|
+
from typed_settings.loaders import TomlFormat
|
13
|
+
from whenever import (
|
14
|
+
Date,
|
15
|
+
DateDelta,
|
16
|
+
DateTimeDelta,
|
17
|
+
PlainDateTime,
|
18
|
+
Time,
|
19
|
+
TimeDelta,
|
20
|
+
ZonedDateTime,
|
21
|
+
)
|
22
|
+
|
23
|
+
from utilities.iterables import always_iterable
|
24
|
+
|
25
|
+
if TYPE_CHECKING:
|
26
|
+
from collections.abc import Callable
|
27
|
+
|
28
|
+
from typed_settings.loaders import Loader
|
29
|
+
from typed_settings.processors import Processor
|
30
|
+
|
31
|
+
from utilities.types import MaybeIterable, PathLike
|
32
|
+
|
33
|
+
|
34
|
+
_T = TypeVar("_T")
|
35
|
+
|
36
|
+
|
37
|
+
##
|
38
|
+
|
39
|
+
|
40
|
+
class ExtendedTSConverter(TSConverter):
|
41
|
+
"""An extension of the TSConverter for custom types."""
|
42
|
+
|
43
|
+
@override
|
44
|
+
def __init__(
|
45
|
+
self,
|
46
|
+
*,
|
47
|
+
resolve_paths: bool = True,
|
48
|
+
strlist_sep: str | Callable[[str], list] | None = ":",
|
49
|
+
) -> None:
|
50
|
+
super().__init__(resolve_paths=resolve_paths, strlist_sep=strlist_sep)
|
51
|
+
cases: list[tuple[type[Any], Callable[..., Any]]] = [
|
52
|
+
(Date, Date.parse_common_iso),
|
53
|
+
(DateDelta, DateDelta.parse_common_iso),
|
54
|
+
(DateTimeDelta, DateTimeDelta.parse_common_iso),
|
55
|
+
(IPv4Address, IPv4Address),
|
56
|
+
(IPv6Address, IPv6Address),
|
57
|
+
(PlainDateTime, PlainDateTime.parse_common_iso),
|
58
|
+
(Time, Time.parse_common_iso),
|
59
|
+
(TimeDelta, TimeDelta.parse_common_iso),
|
60
|
+
(ZonedDateTime, ZonedDateTime.parse_common_iso),
|
61
|
+
]
|
62
|
+
extras = {cls: _make_converter(cls, func) for cls, func in cases}
|
63
|
+
self.scalar_converters |= extras
|
64
|
+
|
65
|
+
|
66
|
+
def _make_converter(
|
67
|
+
cls: type[_T], parser: Callable[[str], _T], /
|
68
|
+
) -> Callable[[Any, type[Any]], Any]:
|
69
|
+
def hook(value: _T | str, _: type[_T] = cls, /) -> Any:
|
70
|
+
if not isinstance(value, (cls, str)): # pragma: no cover
|
71
|
+
msg = f"Invalid type {type(value).__name__!r}; expected '{cls.__name__}' or 'str'"
|
72
|
+
raise TypeError(msg)
|
73
|
+
if isinstance(value, str):
|
74
|
+
return parser(value)
|
75
|
+
return value
|
76
|
+
|
77
|
+
return hook
|
78
|
+
|
79
|
+
|
80
|
+
##
|
81
|
+
|
82
|
+
_BASE_DIR: Path = Path()
|
83
|
+
|
84
|
+
|
85
|
+
def load_settings(
|
86
|
+
cls: type[_T],
|
87
|
+
app_name: str,
|
88
|
+
/,
|
89
|
+
*,
|
90
|
+
filenames: MaybeIterable[str] = "settings.toml",
|
91
|
+
start_dir: PathLike | None = None,
|
92
|
+
loaders: MaybeIterable[Loader] | None = None,
|
93
|
+
processors: MaybeIterable[Processor] = (),
|
94
|
+
base_dir: Path = _BASE_DIR,
|
95
|
+
) -> _T:
|
96
|
+
if not search(r"^[A-Za-z]+(?:_[A-Za-z]+)*$", app_name):
|
97
|
+
raise LoadSettingsError(appname=app_name)
|
98
|
+
filenames_use = list(always_iterable(filenames))
|
99
|
+
start_dir_use = None if start_dir is None else Path(start_dir)
|
100
|
+
files = [find(filename, start_dir=start_dir_use) for filename in filenames_use]
|
101
|
+
file_loader = FileLoader(formats={"*.toml": TomlFormat(app_name)}, files=files)
|
102
|
+
env_loader = EnvLoader(f"{app_name.upper()}__", nested_delimiter="__")
|
103
|
+
loaders_use: list[Loader] = [file_loader, env_loader]
|
104
|
+
if loaders is not None:
|
105
|
+
loaders_use.extend(always_iterable(loaders))
|
106
|
+
return typed_settings.load_settings(
|
107
|
+
cls,
|
108
|
+
loaders_use,
|
109
|
+
processors=list(always_iterable(processors)),
|
110
|
+
converter=ExtendedTSConverter(),
|
111
|
+
base_dir=base_dir,
|
112
|
+
)
|
113
|
+
|
114
|
+
|
115
|
+
@dataclass(kw_only=True, slots=True)
|
116
|
+
class LoadSettingsError(Exception):
|
117
|
+
appname: str
|
118
|
+
|
119
|
+
@override
|
120
|
+
def __str__(self) -> str:
|
121
|
+
return f"Invalid app name; got {self.appname!r}"
|
122
|
+
|
123
|
+
|
124
|
+
__all__ = ["ExtendedTSConverter", "LoadSettingsError", "load_settings"]
|
@@ -1,49 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
from types import NoneType
|
4
|
-
|
5
|
-
from pyrsistent import PTypeError
|
6
|
-
from pytest import raises
|
7
|
-
|
8
|
-
from utilities.pyrsistent import PRecord, field
|
9
|
-
|
10
|
-
|
11
|
-
class TestPRecord:
|
12
|
-
def test_mandatory_field_without_type_checking(self) -> None:
|
13
|
-
class ARecord(PRecord):
|
14
|
-
x: int = field()
|
15
|
-
|
16
|
-
r = ARecord(x=3)
|
17
|
-
assert repr(r) == "ARecord(x=3)"
|
18
|
-
assert r.x == 3
|
19
|
-
assert r.set(x=2) == ARecord(x=2)
|
20
|
-
with raises(
|
21
|
-
AttributeError, match="'y' is not among the specified fields for ARecord"
|
22
|
-
):
|
23
|
-
_ = r.set(y=2)
|
24
|
-
|
25
|
-
def test_optional_field_without_type_checking(self) -> None:
|
26
|
-
class ARecord(PRecord):
|
27
|
-
x: int | None = field(default=None)
|
28
|
-
|
29
|
-
r = ARecord()
|
30
|
-
assert repr(r) == "ARecord(x=None)"
|
31
|
-
assert r.x is None
|
32
|
-
|
33
|
-
def test_mandatory_field_with_type_checking(self) -> None:
|
34
|
-
class ARecord(PRecord):
|
35
|
-
x: int = field(type=int)
|
36
|
-
|
37
|
-
r = ARecord(x=3)
|
38
|
-
assert repr(r) == "ARecord(x=3)"
|
39
|
-
with raises(PTypeError, match="Invalid type for field ARecord.x, was str"):
|
40
|
-
_ = r.set(x="2")
|
41
|
-
|
42
|
-
def test_optional_field_with_type_checking(self) -> None:
|
43
|
-
class ARecord(PRecord):
|
44
|
-
x: int | None = field(type=(int, NoneType), default=None)
|
45
|
-
|
46
|
-
r = ARecord()
|
47
|
-
assert repr(r) == "ARecord(x=None)"
|
48
|
-
with raises(PTypeError, match="Invalid type for field ARecord.x, was str"):
|
49
|
-
_ = r.set(x="2")
|
@@ -1,99 +0,0 @@
|
|
1
|
-
from collections.abc import Callable
|
2
|
-
from dataclasses import dataclass
|
3
|
-
from operator import eq
|
4
|
-
from pathlib import Path
|
5
|
-
from typing import TypeVar
|
6
|
-
|
7
|
-
from hypothesis import given
|
8
|
-
from hypothesis.strategies import DataObject, SearchStrategy, data, tuples
|
9
|
-
from pytest import mark, param
|
10
|
-
from typed_settings import FileLoader, TomlFormat, load_settings
|
11
|
-
from whenever import (
|
12
|
-
Date,
|
13
|
-
DateDelta,
|
14
|
-
DateTimeDelta,
|
15
|
-
PlainDateTime,
|
16
|
-
Time,
|
17
|
-
TimeDelta,
|
18
|
-
ZonedDateTime,
|
19
|
-
)
|
20
|
-
|
21
|
-
from utilities.hypothesis import (
|
22
|
-
date_deltas,
|
23
|
-
date_time_deltas,
|
24
|
-
dates,
|
25
|
-
plain_datetimes,
|
26
|
-
temp_paths,
|
27
|
-
text_ascii,
|
28
|
-
time_deltas,
|
29
|
-
times,
|
30
|
-
zoned_datetimes,
|
31
|
-
)
|
32
|
-
from utilities.typed_settings import ExtendedTSConverter
|
33
|
-
|
34
|
-
app_names = text_ascii(min_size=1).map(str.lower)
|
35
|
-
|
36
|
-
|
37
|
-
_T = TypeVar("_T")
|
38
|
-
|
39
|
-
|
40
|
-
class TestExtendedTSConverter:
|
41
|
-
@given(data=data(), root=temp_paths(), appname=text_ascii(min_size=1))
|
42
|
-
@mark.parametrize(
|
43
|
-
("test_cls", "strategy", "serialize"),
|
44
|
-
[
|
45
|
-
param(Date, dates(), Date.format_common_iso),
|
46
|
-
param(DateDelta, date_deltas(parsable=True), DateDelta.format_common_iso),
|
47
|
-
param(
|
48
|
-
DateTimeDelta,
|
49
|
-
date_time_deltas(parsable=True),
|
50
|
-
DateTimeDelta.format_common_iso,
|
51
|
-
),
|
52
|
-
param(PlainDateTime, plain_datetimes(), PlainDateTime.format_common_iso),
|
53
|
-
param(Time, times(), Time.format_common_iso),
|
54
|
-
param(TimeDelta, time_deltas(), TimeDelta.format_common_iso),
|
55
|
-
param(ZonedDateTime, zoned_datetimes(), ZonedDateTime.format_common_iso),
|
56
|
-
],
|
57
|
-
)
|
58
|
-
def test_main(
|
59
|
-
self,
|
60
|
-
*,
|
61
|
-
data: DataObject,
|
62
|
-
root: Path,
|
63
|
-
appname: str,
|
64
|
-
test_cls: type[_T],
|
65
|
-
strategy: SearchStrategy[_T],
|
66
|
-
serialize: Callable[[_T], str],
|
67
|
-
) -> None:
|
68
|
-
default, value = data.draw(tuples(strategy, strategy))
|
69
|
-
self._run_test(test_cls, default, root, appname, serialize, value, eq)
|
70
|
-
|
71
|
-
def _run_test(
|
72
|
-
self,
|
73
|
-
test_cls: type[_T],
|
74
|
-
default: _T,
|
75
|
-
root: Path,
|
76
|
-
appname: str,
|
77
|
-
serialize: Callable[[_T], str],
|
78
|
-
value: _T,
|
79
|
-
equal: Callable[[_T, _T], bool],
|
80
|
-
/,
|
81
|
-
) -> None:
|
82
|
-
@dataclass(frozen=True, kw_only=True, slots=True)
|
83
|
-
class Settings:
|
84
|
-
value: test_cls = default # pyright: ignore[reportInvalidTypeForm]
|
85
|
-
|
86
|
-
settings_default = load_settings(
|
87
|
-
Settings, loaders=[], converter=ExtendedTSConverter()
|
88
|
-
)
|
89
|
-
assert settings_default.value == default
|
90
|
-
_ = hash(settings_default)
|
91
|
-
file = Path(root, "file.toml")
|
92
|
-
with file.open(mode="w") as fh:
|
93
|
-
_ = fh.write(f'[{appname}]\nvalue = "{serialize(value)}"')
|
94
|
-
settings_loaded = load_settings(
|
95
|
-
Settings,
|
96
|
-
loaders=[FileLoader(formats={"*.toml": TomlFormat(appname)}, files=[file])],
|
97
|
-
converter=ExtendedTSConverter(),
|
98
|
-
)
|
99
|
-
assert equal(settings_loaded.value, value)
|