dycw-utilities 0.168.2__tar.gz → 0.168.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of dycw-utilities might be problematic. Click here for more details.
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/PKG-INFO +4 -4
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/pyproject.toml +11 -15
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_pydantic_settings.py +106 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/__init__.py +1 -1
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/pydantic_settings.py +65 -6
- dycw_utilities-0.168.2/src/tests/test_typed_settings.py +0 -321
- dycw_utilities-0.168.2/src/utilities/typed_settings.py +0 -152
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/.gitignore +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/LICENSE +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/README.md +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/__init__.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/conftest.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/modules/__init__.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/modules/package_missing/__init__.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/modules/package_missing/module.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/modules/package_with/__init__.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/modules/package_with/outer_1.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/modules/package_with/outer_2.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/modules/package_with/subpackage/__init__.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/modules/package_with/subpackage/inner_1.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/modules/package_with/subpackage/inner_2.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/modules/package_with/subpackage/inner_3.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/modules/package_without/__init__.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/modules/package_without/module_1.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/modules/package_without/module_2.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/modules/standalone.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/modules/with_imports.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_aeventkit.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_altair.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_asyncio.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_atomicwrites.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_atools.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_cachetools.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_click.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_concurrent.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_contextlib.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_contextvars.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_cryptography.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_cvxpy.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_dataclasses.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_docker.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_enum.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_errors.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_fastapi.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_fpdf2.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_functions.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_functools.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_getpass.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_git.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_gzip.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_hashlib.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_http.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_hypothesis.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_importlib.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_inflect.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_ipython.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_iterables.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_jinja2.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_json.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_jupyter.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_libcst.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_lightweight_charts.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_logging.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_math.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_memory_profiler.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_modules.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_more_itertools.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_numpy.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_objects/__init__.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_objects/objects.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_operator.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_optuna.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_orjson.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_os.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_parse.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_pathlib.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_pickle.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_platform.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_polars.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_polars_ols.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_postgres.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_pottery.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_pqdm.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_psutil.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_pydantic_settings_sops.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_pyinstrument.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_pytest.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_pytest_randomly.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_pytest_regressions.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_random.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_re.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_redis.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_reprlib.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_scipy.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_sentinel.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_shelve.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_slack_sdk.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_socket.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_sqlalchemy.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_sqlalchemy_polars.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_statsmodels.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_string.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_tempfile.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_testbook.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_text.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_threading.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_timer.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_traceback.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_types.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_typing.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_typing_funcs/__init__.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_typing_funcs/no_future.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_typing_funcs/with_future.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_tzdata.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_tzlocal.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_uuid.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_version.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_warnings.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_whenever.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_zipfile.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/tests/test_zoneinfo.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/aeventkit.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/altair.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/asyncio.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/atomicwrites.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/atools.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/cachetools.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/click.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/concurrent.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/contextlib.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/contextvars.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/cryptography.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/cvxpy.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/dataclasses.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/docker.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/enum.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/errors.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/fastapi.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/fpdf2.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/functions.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/functools.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/getpass.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/git.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/gzip.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/hashlib.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/http.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/hypothesis.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/importlib.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/inflect.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/ipython.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/iterables.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/jinja2.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/json.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/jupyter.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/libcst.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/lightweight_charts.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/logging.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/math.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/memory_profiler.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/modules.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/more_itertools.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/numpy.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/operator.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/optuna.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/orjson.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/os.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/parse.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/pathlib.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/pickle.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/platform.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/polars.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/polars_ols.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/postgres.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/pottery.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/pqdm.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/psutil.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/py.typed +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/pydantic_settings_sops.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/pyinstrument.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/pytest.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/pytest_plugins/__init__.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/pytest_plugins/pytest_randomly.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/pytest_plugins/pytest_regressions.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/pytest_regressions.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/random.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/re.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/redis.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/reprlib.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/scipy.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/sentinel.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/shelve.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/slack_sdk.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/socket.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/sqlalchemy.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/sqlalchemy_polars.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/statsmodels.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/string.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/tempfile.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/testbook.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/text.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/threading.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/timer.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/traceback.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/types.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/typing.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/tzdata.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/tzlocal.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/uuid.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/version.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/warnings.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/whenever.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/zipfile.py +0 -0
- {dycw_utilities-0.168.2 → dycw_utilities-0.168.4}/src/utilities/zoneinfo.py +0 -0
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dycw-utilities
|
|
3
|
-
Version: 0.168.
|
|
3
|
+
Version: 0.168.4
|
|
4
4
|
Author-email: Derek Wan <d.wan@icloud.com>
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Requires-Python: >=3.12
|
|
7
7
|
Requires-Dist: atomicwrites<1.5,>=1.4.1
|
|
8
8
|
Requires-Dist: typing-extensions<4.16,>=4.15.0
|
|
9
9
|
Requires-Dist: tzlocal<5.4,>=5.3.1
|
|
10
|
-
Requires-Dist: whenever<0.10,>=0.9.
|
|
10
|
+
Requires-Dist: whenever<0.10,>=0.9.2
|
|
11
11
|
Provides-Extra: logging
|
|
12
12
|
Requires-Dist: coloredlogs<15.1,>=15.0.1; extra == 'logging'
|
|
13
13
|
Provides-Extra: test
|
|
14
14
|
Requires-Dist: dycw-pytest-only<2.2,>=2.1.1; extra == 'test'
|
|
15
|
-
Requires-Dist: hypothesis<6.141,>=6.140.
|
|
15
|
+
Requires-Dist: hypothesis<6.141,>=6.140.3; extra == 'test'
|
|
16
16
|
Requires-Dist: pytest-asyncio<1.3,>=1.2.0; extra == 'test'
|
|
17
17
|
Requires-Dist: pytest-cov<7.1,>=7.0.0; extra == 'test'
|
|
18
18
|
Requires-Dist: pytest-instafail<0.6,>=0.5.0; extra == 'test'
|
|
19
|
-
Requires-Dist: pytest-lazy-fixtures<1.
|
|
19
|
+
Requires-Dist: pytest-lazy-fixtures<1.5,>=1.4.0; extra == 'test'
|
|
20
20
|
Requires-Dist: pytest-randomly<4.1,>=4.0.1; extra == 'test'
|
|
21
21
|
Requires-Dist: pytest-regressions<2.9,>=2.8.3; extra == 'test'
|
|
22
22
|
Requires-Dist: pytest-repeat<0.10,>=0.9.4; extra == 'test'
|
|
@@ -31,10 +31,10 @@ core = [
|
|
|
31
31
|
"atomicwrites >=1.4.1, <1.5",
|
|
32
32
|
"typing-extensions >=4.15.0, <4.16",
|
|
33
33
|
"tzlocal >=5.3.1, <5.4",
|
|
34
|
-
"whenever >=0.9.
|
|
34
|
+
"whenever >=0.9.2, <0.10",
|
|
35
35
|
]
|
|
36
36
|
cryptography = [
|
|
37
|
-
"cryptography >=46.0.
|
|
37
|
+
"cryptography >=46.0.2, <46.1",
|
|
38
38
|
]
|
|
39
39
|
cvxpy = [
|
|
40
40
|
"cvxpy >=1.7.3, <1.8",
|
|
@@ -52,7 +52,7 @@ dev = [
|
|
|
52
52
|
"pytest-timeout >=2.4.0, <2.5",
|
|
53
53
|
]
|
|
54
54
|
fastapi = [
|
|
55
|
-
"fastapi >=0.
|
|
55
|
+
"fastapi >=0.118.0, <0.119",
|
|
56
56
|
]
|
|
57
57
|
fastapi-test = [
|
|
58
58
|
"httpx",
|
|
@@ -71,7 +71,7 @@ http-test = [
|
|
|
71
71
|
"orjson",
|
|
72
72
|
]
|
|
73
73
|
hypothesis = [
|
|
74
|
-
"hypothesis >=6.140.
|
|
74
|
+
"hypothesis >=6.140.3, <6.141",
|
|
75
75
|
]
|
|
76
76
|
hypothesis-test = [
|
|
77
77
|
"libcst",
|
|
@@ -124,7 +124,7 @@ orjson-test = [
|
|
|
124
124
|
"polars",
|
|
125
125
|
]
|
|
126
126
|
polars = [
|
|
127
|
-
"polars >=1.
|
|
127
|
+
"polars >=1.34.0, <1.35",
|
|
128
128
|
]
|
|
129
129
|
polars-ols = [
|
|
130
130
|
"polars-ols >=0.3.5, <0.4",
|
|
@@ -198,7 +198,7 @@ sklearn = [
|
|
|
198
198
|
"scikit-learn >=1.7.2, <1.8",
|
|
199
199
|
]
|
|
200
200
|
slack-sdk = [
|
|
201
|
-
"slack-sdk >=3.
|
|
201
|
+
"slack-sdk >=3.37.0, <3.38",
|
|
202
202
|
]
|
|
203
203
|
slack-sdk-test = [
|
|
204
204
|
"aiohttp",
|
|
@@ -227,9 +227,6 @@ statsmodels = [
|
|
|
227
227
|
testbook = [
|
|
228
228
|
"testbook >=0.4.2, <0.5",
|
|
229
229
|
]
|
|
230
|
-
typed-settings = [
|
|
231
|
-
"typed-settings >=25.0.0, <25.1",
|
|
232
|
-
]
|
|
233
230
|
tzdata = [
|
|
234
231
|
"tzdata >=2025.2, <2025.3",
|
|
235
232
|
]
|
|
@@ -244,12 +241,12 @@ dependencies = [
|
|
|
244
241
|
"atomicwrites >=1.4.1, <1.5",
|
|
245
242
|
"typing-extensions >=4.15.0, <4.16",
|
|
246
243
|
"tzlocal >=5.3.1, <5.4",
|
|
247
|
-
"whenever >=0.9.
|
|
244
|
+
"whenever >=0.9.2, <0.10",
|
|
248
245
|
]
|
|
249
246
|
name = "dycw-utilities"
|
|
250
247
|
readme = "README.md"
|
|
251
248
|
requires-python = ">= 3.12"
|
|
252
|
-
version = "0.168.
|
|
249
|
+
version = "0.168.4"
|
|
253
250
|
|
|
254
251
|
[project.entry-points.pytest11]
|
|
255
252
|
pytest-randomly = "utilities.pytest_plugins.pytest_randomly"
|
|
@@ -261,12 +258,12 @@ logging = [
|
|
|
261
258
|
]
|
|
262
259
|
test = [
|
|
263
260
|
"dycw-pytest-only >=2.1.1, <2.2",
|
|
264
|
-
"hypothesis >=6.140.
|
|
261
|
+
"hypothesis >=6.140.3, <6.141",
|
|
265
262
|
"pytest >=8.4.2, <8.5",
|
|
266
263
|
"pytest-asyncio >=1.2.0, <1.3",
|
|
267
264
|
"pytest-cov >=7.0.0, <7.1",
|
|
268
265
|
"pytest-instafail >=0.5.0, <0.6",
|
|
269
|
-
"pytest-lazy-fixtures >=1.
|
|
266
|
+
"pytest-lazy-fixtures >=1.4.0, <1.5",
|
|
270
267
|
"pytest-randomly >=4.0.1, <4.1",
|
|
271
268
|
"pytest-regressions >=2.8.3, <2.9",
|
|
272
269
|
"pytest-repeat >=0.9.4, <0.10",
|
|
@@ -282,7 +279,7 @@ test = [
|
|
|
282
279
|
# bump-my-version
|
|
283
280
|
[tool.bumpversion]
|
|
284
281
|
allow_dirty = true
|
|
285
|
-
current_version = "0.168.
|
|
282
|
+
current_version = "0.168.4"
|
|
286
283
|
|
|
287
284
|
[[tool.bumpversion.files]]
|
|
288
285
|
filename = "src/utilities/__init__.py"
|
|
@@ -486,7 +483,6 @@ select = [
|
|
|
486
483
|
"S101", # assert
|
|
487
484
|
"SLF001", # private-member-access
|
|
488
485
|
]
|
|
489
|
-
"src/tests/test_typed_settings.py" = ["I002"] # missing-required-import
|
|
490
486
|
"src/tests/test_typing_funcs/no_future.py" = ["I002"] # missing-required-import
|
|
491
487
|
|
|
492
488
|
[tool.ruff.lint.flake8-tidy-imports]
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
from stat import S_IXUSR
|
|
5
|
+
from subprocess import STDOUT, CalledProcessError, check_output
|
|
4
6
|
from typing import TYPE_CHECKING, ClassVar
|
|
5
7
|
|
|
6
8
|
import tomlkit
|
|
@@ -8,6 +10,7 @@ import yaml
|
|
|
8
10
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
9
11
|
from pytest import mark, param
|
|
10
12
|
|
|
13
|
+
from tests.conftest import SKIPIF_CI_AND_WINDOWS
|
|
11
14
|
from utilities.os import temp_environ
|
|
12
15
|
from utilities.pydantic_settings import (
|
|
13
16
|
CustomBaseSettings,
|
|
@@ -146,3 +149,106 @@ class TestHashableBaseSettings:
|
|
|
146
149
|
|
|
147
150
|
settings = load_settings(Settings)
|
|
148
151
|
_ = hash(settings)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class TestLoadSettings:
|
|
155
|
+
@mark.parametrize(
|
|
156
|
+
("args", "expected"),
|
|
157
|
+
[
|
|
158
|
+
param([], "settings=_Settings(a=1, b=2, inner=_Inner(c=3, d=4))"),
|
|
159
|
+
param(["-a", "5"], "settings=_Settings(a=5, b=2, inner=_Inner(c=3, d=4))"),
|
|
160
|
+
param(
|
|
161
|
+
["--inner.c", "5"],
|
|
162
|
+
"settings=_Settings(a=1, b=2, inner=_Inner(c=5, d=4))",
|
|
163
|
+
),
|
|
164
|
+
param(
|
|
165
|
+
["-h"],
|
|
166
|
+
"""
|
|
167
|
+
usage: script.py [-h] [-a int] [-b int] [--inner [JSON]] [--inner.c int]
|
|
168
|
+
[--inner.d int]
|
|
169
|
+
|
|
170
|
+
options:
|
|
171
|
+
-h, --help show this help message and exit
|
|
172
|
+
-a int (default: 1)
|
|
173
|
+
-b int (default: 2)
|
|
174
|
+
|
|
175
|
+
inner options:
|
|
176
|
+
--inner [JSON] set inner from JSON string (default: {})
|
|
177
|
+
--inner.c int (default: 3)
|
|
178
|
+
--inner.d int (default: 4)
|
|
179
|
+
""",
|
|
180
|
+
),
|
|
181
|
+
],
|
|
182
|
+
)
|
|
183
|
+
@SKIPIF_CI_AND_WINDOWS
|
|
184
|
+
def test_cli(self, *, tmp_path: Path, args: list[str], expected: str) -> None:
|
|
185
|
+
script = tmp_path.joinpath("script.py")
|
|
186
|
+
_ = script.write_text("""\
|
|
187
|
+
#!/usr/bin/env python3
|
|
188
|
+
from __future__ import annotations
|
|
189
|
+
|
|
190
|
+
from collections.abc import Sequence
|
|
191
|
+
from pathlib import Path
|
|
192
|
+
from typing import ClassVar
|
|
193
|
+
|
|
194
|
+
from pydantic_settings import BaseSettings
|
|
195
|
+
|
|
196
|
+
from utilities.pydantic_settings import CustomBaseSettings, PathLikeOrWithSection, load_settings
|
|
197
|
+
|
|
198
|
+
class _Settings(CustomBaseSettings):
|
|
199
|
+
toml_files: ClassVar[Sequence[PathLikeOrWithSection]] = [
|
|
200
|
+
Path(__file__).parent.joinpath("config.toml")
|
|
201
|
+
]
|
|
202
|
+
|
|
203
|
+
a: int
|
|
204
|
+
b: int
|
|
205
|
+
inner: _Inner
|
|
206
|
+
|
|
207
|
+
class _Inner(BaseSettings):
|
|
208
|
+
c: int
|
|
209
|
+
d: int
|
|
210
|
+
|
|
211
|
+
def main() -> None:
|
|
212
|
+
settings = load_settings(_Settings, cli=True)
|
|
213
|
+
print(f"{settings=}")
|
|
214
|
+
|
|
215
|
+
if __name__ == "__main__":
|
|
216
|
+
main()
|
|
217
|
+
""")
|
|
218
|
+
script.chmod(script.stat().st_mode | S_IXUSR)
|
|
219
|
+
config = tmp_path.joinpath("config.toml")
|
|
220
|
+
_ = config.write_text(
|
|
221
|
+
"""\
|
|
222
|
+
a = 1
|
|
223
|
+
b = 2
|
|
224
|
+
|
|
225
|
+
[inner]
|
|
226
|
+
c = 3
|
|
227
|
+
d = 4
|
|
228
|
+
"""
|
|
229
|
+
)
|
|
230
|
+
try:
|
|
231
|
+
result = check_output([script, *args], stderr=STDOUT, text=True).strip("\n")
|
|
232
|
+
except CalledProcessError as error:
|
|
233
|
+
raise RuntimeError(error.stdout) from None
|
|
234
|
+
assert result == expected.strip("\n")
|
|
235
|
+
|
|
236
|
+
def test_cli_coverage(self, *, tmp_path: Path) -> None:
|
|
237
|
+
config = tmp_path.joinpath("config.toml")
|
|
238
|
+
_ = config.write_text("""
|
|
239
|
+
a = 1
|
|
240
|
+
|
|
241
|
+
[inner]
|
|
242
|
+
b = 2""")
|
|
243
|
+
|
|
244
|
+
class Example(CustomBaseSettings):
|
|
245
|
+
toml_files: ClassVar[Sequence[PathLikeOrWithSection]] = [config]
|
|
246
|
+
|
|
247
|
+
a: int
|
|
248
|
+
inner: _Inner
|
|
249
|
+
|
|
250
|
+
class _Inner(BaseSettings):
|
|
251
|
+
b: int
|
|
252
|
+
|
|
253
|
+
_ = Example.model_rebuild()
|
|
254
|
+
_ = load_settings(Example, cli=True)
|
|
@@ -2,10 +2,12 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from functools import reduce
|
|
4
4
|
from pathlib import Path
|
|
5
|
-
from typing import TYPE_CHECKING, Any, ClassVar, assert_never, override
|
|
5
|
+
from typing import TYPE_CHECKING, Any, ClassVar, assert_never, cast, override
|
|
6
6
|
|
|
7
|
+
from pydantic import Field, create_model
|
|
7
8
|
from pydantic_settings import (
|
|
8
9
|
BaseSettings,
|
|
10
|
+
CliSettingsSource,
|
|
9
11
|
JsonConfigSettingsSource,
|
|
10
12
|
PydanticBaseSettingsSource,
|
|
11
13
|
SettingsConfigDict,
|
|
@@ -14,6 +16,7 @@ from pydantic_settings import (
|
|
|
14
16
|
)
|
|
15
17
|
from pydantic_settings.sources import DEFAULT_PATH
|
|
16
18
|
|
|
19
|
+
from utilities.errors import ImpossibleCaseError
|
|
17
20
|
from utilities.iterables import always_iterable
|
|
18
21
|
|
|
19
22
|
if TYPE_CHECKING:
|
|
@@ -76,11 +79,6 @@ class CustomBaseSettings(BaseSettings):
|
|
|
76
79
|
)
|
|
77
80
|
|
|
78
81
|
|
|
79
|
-
def load_settings[T: BaseSettings](cls: type[T], /) -> T:
|
|
80
|
-
"""Load a set of settings."""
|
|
81
|
-
return cls()
|
|
82
|
-
|
|
83
|
-
|
|
84
82
|
class JsonConfigSectionSettingsSource(JsonConfigSettingsSource):
|
|
85
83
|
@override
|
|
86
84
|
def __init__(
|
|
@@ -168,6 +166,67 @@ class HashableBaseSettings(BaseSettings):
|
|
|
168
166
|
model_config: ClassVar[SettingsConfigDict] = SettingsConfigDict(frozen=True)
|
|
169
167
|
|
|
170
168
|
|
|
169
|
+
##
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def load_settings[T: BaseSettings](cls: type[T], /, *, cli: bool = False) -> T:
|
|
173
|
+
"""Load a set of settings."""
|
|
174
|
+
_ = cls.model_rebuild()
|
|
175
|
+
if cli:
|
|
176
|
+
cls_with_defaults = _load_settings_create_model(cls)
|
|
177
|
+
|
|
178
|
+
@classmethod
|
|
179
|
+
def settings_customise_sources(
|
|
180
|
+
cls: type[BaseSettings],
|
|
181
|
+
settings_cls: type[BaseSettings],
|
|
182
|
+
init_settings: PydanticBaseSettingsSource,
|
|
183
|
+
env_settings: PydanticBaseSettingsSource,
|
|
184
|
+
dotenv_settings: PydanticBaseSettingsSource,
|
|
185
|
+
file_secret_settings: PydanticBaseSettingsSource,
|
|
186
|
+
) -> tuple[PydanticBaseSettingsSource, ...]:
|
|
187
|
+
parent = cast(
|
|
188
|
+
"Any", super(cls_with_defaults, cls)
|
|
189
|
+
).settings_customise_sources(
|
|
190
|
+
settings_cls=settings_cls,
|
|
191
|
+
init_settings=init_settings,
|
|
192
|
+
env_settings=env_settings,
|
|
193
|
+
dotenv_settings=dotenv_settings,
|
|
194
|
+
file_secret_settings=file_secret_settings,
|
|
195
|
+
)
|
|
196
|
+
return (
|
|
197
|
+
CliSettingsSource(
|
|
198
|
+
settings_cls, cli_parse_args=True, case_sensitive=False
|
|
199
|
+
),
|
|
200
|
+
*parent,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
cls_use = type(
|
|
204
|
+
cls.__name__,
|
|
205
|
+
(cls_with_defaults,),
|
|
206
|
+
{"settings_customise_sources": settings_customise_sources},
|
|
207
|
+
)
|
|
208
|
+
cls_use = cast("type[T]", cls_use)
|
|
209
|
+
else:
|
|
210
|
+
cls_use = cls
|
|
211
|
+
return cls_use()
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _load_settings_create_model[T: BaseSettings](
|
|
215
|
+
cls: type[T], /, *, values: T | None = None
|
|
216
|
+
) -> type[T]:
|
|
217
|
+
values_use = cls() if values is None else values
|
|
218
|
+
kwargs: dict[str, Any] = {}
|
|
219
|
+
for name, field in cls.model_fields.items():
|
|
220
|
+
if (ann := field.annotation) is None:
|
|
221
|
+
raise ImpossibleCaseError(case=[f"{ann=}"]) # pragma: no cover
|
|
222
|
+
value = getattr(values_use, name)
|
|
223
|
+
if issubclass(ann, BaseSettings):
|
|
224
|
+
kwargs[name] = _load_settings_create_model(ann, values=value)
|
|
225
|
+
else:
|
|
226
|
+
kwargs[name] = (field.annotation, Field(default=value))
|
|
227
|
+
return create_model(cls.__name__, __base__=cls, **kwargs)
|
|
228
|
+
|
|
229
|
+
|
|
171
230
|
__all__ = [
|
|
172
231
|
"CustomBaseSettings",
|
|
173
232
|
"HashableBaseSettings",
|
|
@@ -1,321 +0,0 @@
|
|
|
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 Any, ClassVar, Self, assert_never, override
|
|
6
|
-
from uuid import UUID
|
|
7
|
-
|
|
8
|
-
import typed_settings
|
|
9
|
-
from hypothesis import given
|
|
10
|
-
from hypothesis.strategies import (
|
|
11
|
-
DataObject,
|
|
12
|
-
SearchStrategy,
|
|
13
|
-
booleans,
|
|
14
|
-
data,
|
|
15
|
-
integers,
|
|
16
|
-
ip_addresses,
|
|
17
|
-
sampled_from,
|
|
18
|
-
tuples,
|
|
19
|
-
uuids,
|
|
20
|
-
)
|
|
21
|
-
from pytest import mark, param, raises
|
|
22
|
-
from typed_settings import EnvLoader, FileLoader, TomlFormat
|
|
23
|
-
from whenever import (
|
|
24
|
-
Date,
|
|
25
|
-
DateDelta,
|
|
26
|
-
DateTimeDelta,
|
|
27
|
-
MonthDay,
|
|
28
|
-
PlainDateTime,
|
|
29
|
-
Time,
|
|
30
|
-
TimeDelta,
|
|
31
|
-
YearMonth,
|
|
32
|
-
ZonedDateTime,
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
from utilities.hypothesis import (
|
|
36
|
-
date_deltas,
|
|
37
|
-
date_time_deltas,
|
|
38
|
-
dates,
|
|
39
|
-
month_days,
|
|
40
|
-
paths,
|
|
41
|
-
plain_date_times,
|
|
42
|
-
temp_paths,
|
|
43
|
-
text_ascii,
|
|
44
|
-
time_deltas,
|
|
45
|
-
times,
|
|
46
|
-
year_months,
|
|
47
|
-
zoned_date_times,
|
|
48
|
-
)
|
|
49
|
-
from utilities.os import temp_environ
|
|
50
|
-
from utilities.re import extract_group
|
|
51
|
-
from utilities.sentinel import Sentinel, sentinel
|
|
52
|
-
from utilities.text import strip_and_dedent
|
|
53
|
-
from utilities.typed_settings import (
|
|
54
|
-
ExtendedTSConverter,
|
|
55
|
-
LoadSettingsError,
|
|
56
|
-
load_settings,
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
app_names = text_ascii(min_size=1).map(str.lower)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
@dataclass(kw_only=True, slots=True)
|
|
63
|
-
class _Case[T]:
|
|
64
|
-
cls: type[T]
|
|
65
|
-
strategy: SearchStrategy[T]
|
|
66
|
-
serialize: Callable[[T], str]
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
class TestExtendedTSConverter:
|
|
70
|
-
cases: ClassVar[list[_Case]] = [
|
|
71
|
-
_Case(cls=Date, strategy=dates(), serialize=str),
|
|
72
|
-
_Case(cls=DateDelta, strategy=date_deltas(parsable=True), serialize=str),
|
|
73
|
-
_Case(
|
|
74
|
-
cls=DateTimeDelta, strategy=date_time_deltas(parsable=True), serialize=str
|
|
75
|
-
),
|
|
76
|
-
_Case(cls=IPv4Address, strategy=ip_addresses(v=4), serialize=str),
|
|
77
|
-
_Case(cls=IPv6Address, strategy=ip_addresses(v=6), serialize=str),
|
|
78
|
-
_Case(cls=MonthDay, strategy=month_days(), serialize=str),
|
|
79
|
-
_Case(cls=PlainDateTime, strategy=plain_date_times(), serialize=str),
|
|
80
|
-
_Case(cls=Time, strategy=times(), serialize=str),
|
|
81
|
-
_Case(cls=TimeDelta, strategy=time_deltas(), serialize=str),
|
|
82
|
-
_Case(cls=UUID, strategy=uuids(), serialize=str),
|
|
83
|
-
_Case(cls=YearMonth, strategy=year_months(), serialize=str),
|
|
84
|
-
_Case(cls=ZonedDateTime, strategy=zoned_date_times(), serialize=str),
|
|
85
|
-
]
|
|
86
|
-
|
|
87
|
-
@given(data=data())
|
|
88
|
-
@mark.parametrize(("cls", "strategy"), [param(c.cls, c.strategy) for c in cases])
|
|
89
|
-
def test_default(
|
|
90
|
-
self, *, data: DataObject, cls: type[Any], strategy: SearchStrategy[Any]
|
|
91
|
-
) -> None:
|
|
92
|
-
default = data.draw(strategy)
|
|
93
|
-
|
|
94
|
-
@dataclass(frozen=True, kw_only=True, slots=True)
|
|
95
|
-
class Settings:
|
|
96
|
-
value: cls = default # pyright: ignore[reportInvalidTypeForm]
|
|
97
|
-
|
|
98
|
-
loaded = typed_settings.load_settings(
|
|
99
|
-
Settings, loaders=[], converter=ExtendedTSConverter()
|
|
100
|
-
)
|
|
101
|
-
assert loaded.value == default
|
|
102
|
-
|
|
103
|
-
@given(data=data(), root=temp_paths(), app_name=app_names)
|
|
104
|
-
@mark.parametrize(
|
|
105
|
-
("cls", "strategy", "serialize"),
|
|
106
|
-
[param(c.cls, c.strategy, c.serialize) for c in cases],
|
|
107
|
-
)
|
|
108
|
-
def test_loaded(
|
|
109
|
-
self,
|
|
110
|
-
*,
|
|
111
|
-
data: DataObject,
|
|
112
|
-
root: Path,
|
|
113
|
-
app_name: str,
|
|
114
|
-
cls: type[Any],
|
|
115
|
-
strategy: SearchStrategy[Any],
|
|
116
|
-
serialize: Callable[[Any], str],
|
|
117
|
-
) -> None:
|
|
118
|
-
default, value = data.draw(tuples(strategy, strategy))
|
|
119
|
-
|
|
120
|
-
@dataclass(frozen=True, kw_only=True, slots=True)
|
|
121
|
-
class Settings:
|
|
122
|
-
value: cls = default # pyright: ignore[reportInvalidTypeForm]
|
|
123
|
-
|
|
124
|
-
file = Path(root, "file.toml")
|
|
125
|
-
_ = file.write_text(
|
|
126
|
-
strip_and_dedent(f"""
|
|
127
|
-
[{app_name}]
|
|
128
|
-
value = '{serialize(value)}'
|
|
129
|
-
""")
|
|
130
|
-
)
|
|
131
|
-
loaded = typed_settings.load_settings(
|
|
132
|
-
Settings,
|
|
133
|
-
loaders=[
|
|
134
|
-
FileLoader(formats={"*.toml": TomlFormat(app_name)}, files=[file])
|
|
135
|
-
],
|
|
136
|
-
converter=ExtendedTSConverter(),
|
|
137
|
-
)
|
|
138
|
-
assert loaded.value == value
|
|
139
|
-
|
|
140
|
-
@given(
|
|
141
|
-
root=temp_paths(),
|
|
142
|
-
app_name=app_names,
|
|
143
|
-
env_name=text_ascii(min_size=1).map(lambda text: f"TEST_{text}".upper()),
|
|
144
|
-
env_value=text_ascii(min_size=1),
|
|
145
|
-
)
|
|
146
|
-
def test_path_env_var(
|
|
147
|
-
self, *, root: str, app_name: str, env_name: str, env_value: str
|
|
148
|
-
) -> None:
|
|
149
|
-
@dataclass(frozen=True, kw_only=True, slots=True)
|
|
150
|
-
class Settings:
|
|
151
|
-
value: Path
|
|
152
|
-
|
|
153
|
-
file = Path(root, "file.toml")
|
|
154
|
-
_ = file.write_text(
|
|
155
|
-
strip_and_dedent(f"""
|
|
156
|
-
[{app_name}]
|
|
157
|
-
value = '${env_name}'
|
|
158
|
-
""")
|
|
159
|
-
)
|
|
160
|
-
with temp_environ({env_name: env_value}):
|
|
161
|
-
settings = typed_settings.load_settings(
|
|
162
|
-
Settings,
|
|
163
|
-
loaders=[
|
|
164
|
-
FileLoader(formats={"*.toml": TomlFormat(app_name)}, files=[file])
|
|
165
|
-
],
|
|
166
|
-
converter=ExtendedTSConverter(resolve_paths=False),
|
|
167
|
-
)
|
|
168
|
-
expected = Path(env_value)
|
|
169
|
-
assert settings.value == expected
|
|
170
|
-
|
|
171
|
-
@given(root=temp_paths(), app_name=app_names, path=paths(), resolve=booleans())
|
|
172
|
-
def test_path_resolution(
|
|
173
|
-
self, *, root: str, app_name: str, path: Path, resolve: bool
|
|
174
|
-
) -> None:
|
|
175
|
-
@dataclass(frozen=True, kw_only=True, slots=True)
|
|
176
|
-
class Settings:
|
|
177
|
-
value: Path
|
|
178
|
-
|
|
179
|
-
file = Path(root, "file.toml")
|
|
180
|
-
_ = file.write_text(
|
|
181
|
-
strip_and_dedent(f"""
|
|
182
|
-
[{app_name}]
|
|
183
|
-
value = '{path!s}'
|
|
184
|
-
""")
|
|
185
|
-
)
|
|
186
|
-
settings = typed_settings.load_settings(
|
|
187
|
-
Settings,
|
|
188
|
-
loaders=[
|
|
189
|
-
FileLoader(formats={"*.toml": TomlFormat(app_name)}, files=[file])
|
|
190
|
-
],
|
|
191
|
-
converter=ExtendedTSConverter(resolve_paths=resolve),
|
|
192
|
-
)
|
|
193
|
-
match resolve:
|
|
194
|
-
case True:
|
|
195
|
-
expected = Path.cwd().joinpath(path)
|
|
196
|
-
case False:
|
|
197
|
-
expected = Path(path)
|
|
198
|
-
case never:
|
|
199
|
-
assert_never(never)
|
|
200
|
-
assert settings.value == expected
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
class TestLoadSettings:
|
|
204
|
-
@given(root=temp_paths(), date_time=zoned_date_times(), app_name=app_names)
|
|
205
|
-
def test_main(self, *, root: Path, date_time: ZonedDateTime, app_name: str) -> None:
|
|
206
|
-
@dataclass(frozen=True, kw_only=True, slots=True)
|
|
207
|
-
class Settings:
|
|
208
|
-
date_time: ZonedDateTime
|
|
209
|
-
|
|
210
|
-
file = root.joinpath("settings.toml")
|
|
211
|
-
_ = file.write_text(
|
|
212
|
-
strip_and_dedent(f"""
|
|
213
|
-
[{app_name}]
|
|
214
|
-
date_time = {str(date_time)!r}
|
|
215
|
-
""")
|
|
216
|
-
)
|
|
217
|
-
settings = load_settings(Settings, app_name, start_dir=root)
|
|
218
|
-
assert settings.date_time == date_time
|
|
219
|
-
|
|
220
|
-
@given(
|
|
221
|
-
prefix=app_names.map(lambda text: f"TEST_{text}".upper()),
|
|
222
|
-
date_time=zoned_date_times(),
|
|
223
|
-
app_name=app_names,
|
|
224
|
-
)
|
|
225
|
-
def test_loaders(
|
|
226
|
-
self, *, prefix: str, date_time: ZonedDateTime, app_name: str
|
|
227
|
-
) -> None:
|
|
228
|
-
key = f"{prefix}__DATE_TIME"
|
|
229
|
-
|
|
230
|
-
@dataclass(frozen=True, kw_only=True, slots=True)
|
|
231
|
-
class Settings:
|
|
232
|
-
date_time: ZonedDateTime
|
|
233
|
-
|
|
234
|
-
with temp_environ({key: str(date_time)}):
|
|
235
|
-
settings = load_settings(
|
|
236
|
-
Settings, app_name, loaders=[EnvLoader(prefix=f"{prefix}__")]
|
|
237
|
-
)
|
|
238
|
-
assert settings.date_time == date_time
|
|
239
|
-
|
|
240
|
-
@given(root=temp_paths(), app_name=app_names)
|
|
241
|
-
def test_converter_simple(self, *, root: Path, app_name: str) -> None:
|
|
242
|
-
@dataclass(frozen=True, kw_only=True, slots=True)
|
|
243
|
-
class Settings:
|
|
244
|
-
sentinel: Sentinel
|
|
245
|
-
|
|
246
|
-
file = root.joinpath("settings.toml")
|
|
247
|
-
_ = file.write_text(
|
|
248
|
-
strip_and_dedent(f"""
|
|
249
|
-
[{app_name}]
|
|
250
|
-
sentinel = 'sentinel'
|
|
251
|
-
""")
|
|
252
|
-
)
|
|
253
|
-
|
|
254
|
-
def convert(text: str, /) -> Sentinel:
|
|
255
|
-
if text == "sentinel":
|
|
256
|
-
return sentinel
|
|
257
|
-
raise ValueError
|
|
258
|
-
|
|
259
|
-
settings = load_settings(
|
|
260
|
-
Settings, app_name, start_dir=root, converters=[(Sentinel, convert)]
|
|
261
|
-
)
|
|
262
|
-
assert settings.sentinel is sentinel
|
|
263
|
-
|
|
264
|
-
@given(data=data(), root=temp_paths(), n=integers(), app_name=app_names)
|
|
265
|
-
def test_converter_dataclass(
|
|
266
|
-
self, *, data: DataObject, root: Path, n: int, app_name: str
|
|
267
|
-
) -> None:
|
|
268
|
-
@dataclass(repr=False, frozen=True, kw_only=True, slots=True)
|
|
269
|
-
class Left:
|
|
270
|
-
x: int
|
|
271
|
-
|
|
272
|
-
@override
|
|
273
|
-
def __str__(self) -> str:
|
|
274
|
-
return f"left{self.x}"
|
|
275
|
-
|
|
276
|
-
@classmethod
|
|
277
|
-
def parse(cls, text: str, /) -> Self:
|
|
278
|
-
x = extract_group(r"^left(.+?)$", text)
|
|
279
|
-
return cls(x=int(x))
|
|
280
|
-
|
|
281
|
-
@dataclass(frozen=True, kw_only=True, slots=True)
|
|
282
|
-
class Right:
|
|
283
|
-
y: int
|
|
284
|
-
|
|
285
|
-
@override
|
|
286
|
-
def __str__(self) -> str:
|
|
287
|
-
return f"right{self.y}"
|
|
288
|
-
|
|
289
|
-
@classmethod
|
|
290
|
-
def parse(cls, text: str, /) -> Self:
|
|
291
|
-
y = extract_group(r"^right(.+?)$", text)
|
|
292
|
-
return cls(y=int(y))
|
|
293
|
-
|
|
294
|
-
value = data.draw(sampled_from([Left(x=n), Right(y=n)]))
|
|
295
|
-
|
|
296
|
-
@dataclass(frozen=True, kw_only=True, slots=True)
|
|
297
|
-
class Settings:
|
|
298
|
-
inner: Left | Right
|
|
299
|
-
|
|
300
|
-
file = root.joinpath("settings.toml")
|
|
301
|
-
_ = file.write_text(
|
|
302
|
-
strip_and_dedent(f"""
|
|
303
|
-
[{app_name}]
|
|
304
|
-
inner = {str(value)!r}
|
|
305
|
-
""")
|
|
306
|
-
)
|
|
307
|
-
settings = load_settings(
|
|
308
|
-
Settings,
|
|
309
|
-
app_name,
|
|
310
|
-
start_dir=root,
|
|
311
|
-
converters=[(Left, Left.parse), (Right, Right.parse)],
|
|
312
|
-
)
|
|
313
|
-
assert settings.inner == value
|
|
314
|
-
|
|
315
|
-
@mark.parametrize("app_name", [param("app_"), param("app1"), param("app__name")])
|
|
316
|
-
def test_error(self, *, app_name: str) -> None:
|
|
317
|
-
@dataclass(frozen=True, kw_only=True, slots=True)
|
|
318
|
-
class Settings: ...
|
|
319
|
-
|
|
320
|
-
with raises(LoadSettingsError, match=r"Invalid app name; got '.+'"):
|
|
321
|
-
_ = load_settings(Settings, app_name)
|