dycw-utilities 0.108.1__tar.gz → 0.108.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.108.1 → dycw_utilities-0.108.3}/PKG-INFO +1 -1
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/pyproject.toml +2 -2
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_functions.py +21 -0
- dycw_utilities-0.108.3/src/tests/test_parse.py +242 -0
- dycw_utilities-0.108.3/src/tests/test_python_dotenv.py +148 -0
- dycw_utilities-0.108.3/src/tests/test_sentinel.py +45 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_text.py +21 -4
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/__init__.py +1 -1
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/functions.py +28 -0
- dycw_utilities-0.108.3/src/utilities/parse.py +129 -0
- dycw_utilities-0.108.3/src/utilities/python_dotenv.py +116 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/sentinel.py +28 -1
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/text.py +22 -1
- dycw_utilities-0.108.1/src/tests/test_python_dotenv.py +0 -452
- dycw_utilities-0.108.1/src/tests/test_sentinel.py +0 -22
- dycw_utilities-0.108.1/src/utilities/python_dotenv.py +0 -259
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/.gitignore +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/LICENSE +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/README.md +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/__init__.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/conftest.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/modules/__init__.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/modules/package_missing/__init__.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/modules/package_missing/module.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/modules/package_with/__init__.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/modules/package_with/outer_1.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/modules/package_with/outer_2.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/modules/package_with/subpackage/__init__.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/modules/package_with/subpackage/inner_1.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/modules/package_with/subpackage/inner_2.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/modules/package_with/subpackage/inner_3.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/modules/package_without/__init__.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/modules/package_without/module_1.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/modules/package_without/module_2.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/modules/standalone.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/modules/with_imports.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/scripts/__init__.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/scripts/test_async_service/__init__.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/scripts/test_async_service/__main__.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/scripts/test_async_service/run.sh +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/scripts/test_queue_processor/__init__.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/scripts/test_queue_processor/__main__.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/scripts/test_queue_processor/run.sh +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_altair.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_astor.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_asyncio.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_atomicwrites.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_atools.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_cachetools.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_click.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_concurrent.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_contextlib.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_contextvars.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_cryptography.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_cvxpy.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_dataclasses.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_datetime.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_enum.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_errors.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_eventkit.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_fastapi.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_fpdf2.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_functools.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_getpass.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_git.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_hashlib.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_http.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_hypothesis.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_ipython.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_iterables.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_jupyter.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_logging.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_loguru.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_luigi.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_math.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_memory_profiler.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_modules.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_more_itertools.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_numpy.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_operator.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_optuna.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_orjson.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_os.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_pathlib.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_period.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_pickle.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_platform.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_polars.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_pqdm.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_pydantic.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_pyinstrument.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_pyrsistent.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_pytest.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_pytest_regressions.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_random.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_re.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_redis.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_reprlib.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_rich.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_scipy.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_shelve.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_slack_sdk.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_socket.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_sqlalchemy.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_sqlalchemy_polars.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_streamlit.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_sys.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_tempfile.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_tenacity.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_threading.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_timer.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_traceback.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_traceback_funcs/__init__.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_traceback_funcs/chain.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_traceback_funcs/decorated_async.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_traceback_funcs/decorated_sync.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_traceback_funcs/error_bind.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_traceback_funcs/many.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_traceback_funcs/one.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_traceback_funcs/recursive.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_traceback_funcs/task_group_one.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_traceback_funcs/task_group_two.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_traceback_funcs/two.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_traceback_funcs/untraced.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_types.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_typing.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_typing_funcs/__init__.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_typing_funcs/no_future.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_typing_funcs/with_future.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_tzdata.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_tzlocal.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_uuid.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_version.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_warnings.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_whenever.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_zipfile.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/tests/test_zoneinfo.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/altair.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/astor.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/asyncio.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/atomicwrites.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/atools.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/cachetools.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/click.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/concurrent.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/contextlib.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/contextvars.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/cryptography.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/cvxpy.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/dataclasses.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/datetime.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/enum.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/errors.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/eventkit.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/fastapi.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/fpdf2.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/functools.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/getpass.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/git.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/hashlib.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/http.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/hypothesis.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/ipython.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/iterables.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/jupyter.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/logging.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/loguru.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/luigi.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/math.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/memory_profiler.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/modules.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/more_itertools.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/numpy.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/operator.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/optuna.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/orjson.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/os.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/pathlib.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/period.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/pickle.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/platform.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/polars.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/pqdm.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/py.typed +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/pydantic.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/pyinstrument.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/pyrsistent.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/pytest.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/pytest_regressions.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/random.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/re.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/redis.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/reprlib.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/rich.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/scipy.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/shelve.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/slack_sdk.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/socket.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/sqlalchemy.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/sqlalchemy_polars.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/streamlit.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/sys.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/tempfile.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/tenacity.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/threading.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/timer.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/traceback.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/types.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/typing.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/tzdata.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/tzlocal.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/uuid.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/version.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/warnings.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/whenever.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/zipfile.py +0 -0
- {dycw_utilities-0.108.1 → dycw_utilities-0.108.3}/src/utilities/zoneinfo.py +0 -0
@@ -89,7 +89,7 @@ dependencies = [
|
|
89
89
|
name = "dycw-utilities"
|
90
90
|
readme = "README.md"
|
91
91
|
requires-python = ">= 3.12"
|
92
|
-
version = "0.108.
|
92
|
+
version = "0.108.3"
|
93
93
|
|
94
94
|
[project.optional-dependencies]
|
95
95
|
test = [
|
@@ -332,7 +332,7 @@ zzz-test-zoneinfo = [
|
|
332
332
|
# bump-my-version
|
333
333
|
[tool.bumpversion]
|
334
334
|
allow_dirty = true
|
335
|
-
current_version = "0.108.
|
335
|
+
current_version = "0.108.3"
|
336
336
|
|
337
337
|
[[tool.bumpversion.files]]
|
338
338
|
filename = "src/utilities/__init__.py"
|
@@ -5,6 +5,7 @@ from dataclasses import dataclass
|
|
5
5
|
from functools import cache, cached_property, lru_cache, partial, wraps
|
6
6
|
from itertools import chain
|
7
7
|
from operator import neg
|
8
|
+
from pathlib import Path
|
8
9
|
from types import NoneType
|
9
10
|
from typing import TYPE_CHECKING, Any, ClassVar, ParamSpec, TypeVar, cast
|
10
11
|
|
@@ -37,6 +38,7 @@ from utilities.functions import (
|
|
37
38
|
EnsureMemberError,
|
38
39
|
EnsureNotNoneError,
|
39
40
|
EnsureNumberError,
|
41
|
+
EnsurePathError,
|
40
42
|
EnsureSizedError,
|
41
43
|
EnsureSizedNotStrError,
|
42
44
|
EnsureStrError,
|
@@ -56,6 +58,7 @@ from utilities.functions import (
|
|
56
58
|
ensure_member,
|
57
59
|
ensure_not_none,
|
58
60
|
ensure_number,
|
61
|
+
ensure_path,
|
59
62
|
ensure_sized,
|
60
63
|
ensure_sized_not_str,
|
61
64
|
ensure_str,
|
@@ -336,6 +339,24 @@ class TestEnsureNumber:
|
|
336
339
|
_ = ensure_number(sentinel, nullable=nullable)
|
337
340
|
|
338
341
|
|
342
|
+
class TestEnsurePath:
|
343
|
+
@given(case=sampled_from([(Path.home(), False), (Path.home(), True), (None, True)]))
|
344
|
+
def test_main(self, *, case: tuple[int | None, bool]) -> None:
|
345
|
+
obj, nullable = case
|
346
|
+
_ = ensure_path(obj, nullable=nullable)
|
347
|
+
|
348
|
+
@given(
|
349
|
+
case=sampled_from([
|
350
|
+
(False, "Object '.*' of type '.*' must be a Path"),
|
351
|
+
(True, "Object '.*' of type '.*' must be a Path or None"),
|
352
|
+
])
|
353
|
+
)
|
354
|
+
def test_error(self, *, case: tuple[bool, str]) -> None:
|
355
|
+
nullable, match = case
|
356
|
+
with raises(EnsurePathError, match=match):
|
357
|
+
_ = ensure_path(sentinel, nullable=nullable)
|
358
|
+
|
359
|
+
|
339
360
|
class TestEnsureSized:
|
340
361
|
@given(obj=sampled_from([[], (), ""]))
|
341
362
|
def test_main(self, *, obj: Any) -> None:
|
@@ -0,0 +1,242 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import datetime as dt
|
4
|
+
from collections.abc import Iterable
|
5
|
+
from dataclasses import dataclass
|
6
|
+
from pathlib import Path
|
7
|
+
from types import NoneType
|
8
|
+
from typing import Literal
|
9
|
+
|
10
|
+
from hypothesis import given
|
11
|
+
from hypothesis.strategies import booleans, dates, floats, integers, sampled_from, times
|
12
|
+
from pytest import raises
|
13
|
+
|
14
|
+
from tests.test_operator import TruthEnum
|
15
|
+
from utilities.functions import ensure_path
|
16
|
+
from utilities.hypothesis import (
|
17
|
+
local_datetimes,
|
18
|
+
paths,
|
19
|
+
text_ascii,
|
20
|
+
timedeltas_2w,
|
21
|
+
versions,
|
22
|
+
zoned_datetimes,
|
23
|
+
)
|
24
|
+
from utilities.math import is_equal
|
25
|
+
from utilities.parse import ParseTextError, parse_text
|
26
|
+
from utilities.sentinel import Sentinel, sentinel
|
27
|
+
from utilities.version import Version
|
28
|
+
from utilities.whenever import (
|
29
|
+
serialize_date,
|
30
|
+
serialize_datetime,
|
31
|
+
serialize_time,
|
32
|
+
serialize_timedelta,
|
33
|
+
)
|
34
|
+
|
35
|
+
|
36
|
+
class TestParseText:
|
37
|
+
@given(value=booleans())
|
38
|
+
def test_bool(self, *, value: bool) -> None:
|
39
|
+
text = str(value)
|
40
|
+
result = parse_text(bool, text)
|
41
|
+
assert result is value
|
42
|
+
|
43
|
+
@given(date=dates())
|
44
|
+
def test_date(self, *, date: dt.date) -> None:
|
45
|
+
text = serialize_date(date)
|
46
|
+
result = parse_text(dt.date, text)
|
47
|
+
assert result == date
|
48
|
+
|
49
|
+
@given(datetime=local_datetimes() | zoned_datetimes())
|
50
|
+
def test_datetime(self, *, datetime: dt.datetime) -> None:
|
51
|
+
text = serialize_datetime(datetime)
|
52
|
+
result = parse_text(dt.datetime, text)
|
53
|
+
assert result == datetime
|
54
|
+
|
55
|
+
@given(truth=sampled_from(TruthEnum))
|
56
|
+
def test_enum(self, *, truth: TruthEnum) -> None:
|
57
|
+
text = truth.name
|
58
|
+
result = parse_text(TruthEnum, text)
|
59
|
+
assert result is truth
|
60
|
+
|
61
|
+
@given(value=floats())
|
62
|
+
def test_float(self, *, value: float) -> None:
|
63
|
+
text = str(value)
|
64
|
+
result = parse_text(float, text)
|
65
|
+
assert is_equal(result, value)
|
66
|
+
|
67
|
+
@given(value=integers())
|
68
|
+
def test_int(self, *, value: int) -> None:
|
69
|
+
text = str(value)
|
70
|
+
result = parse_text(int, text)
|
71
|
+
assert result == value
|
72
|
+
|
73
|
+
@given(truth=sampled_from(["true", "false"]))
|
74
|
+
def test_literal(self, *, truth: Literal["true", "false"]) -> None:
|
75
|
+
result = parse_text(Literal["true", "false"], truth)
|
76
|
+
assert result == truth
|
77
|
+
|
78
|
+
def test_nullable_int_none(self) -> None:
|
79
|
+
text = str(None)
|
80
|
+
result = parse_text(int | None, text)
|
81
|
+
assert result is None
|
82
|
+
|
83
|
+
@given(value=integers())
|
84
|
+
def test_nullable_int_int(self, *, value: int) -> None:
|
85
|
+
text = str(value)
|
86
|
+
result = parse_text(int | None, text)
|
87
|
+
assert result == value
|
88
|
+
|
89
|
+
def test_none(self) -> None:
|
90
|
+
text = str(None)
|
91
|
+
result = parse_text(None, text)
|
92
|
+
assert result is None
|
93
|
+
|
94
|
+
def test_none_type(self) -> None:
|
95
|
+
text = str(None)
|
96
|
+
result = parse_text(NoneType, text)
|
97
|
+
assert result is None
|
98
|
+
|
99
|
+
@given(path=paths())
|
100
|
+
def test_path(self, *, path: Path) -> None:
|
101
|
+
text = str(path)
|
102
|
+
result = parse_text(Path, text)
|
103
|
+
assert result == path
|
104
|
+
|
105
|
+
@given(path=paths())
|
106
|
+
def test_path_expanded(self, *, path: Path) -> None:
|
107
|
+
path_use = Path("~", path)
|
108
|
+
text = str(path_use)
|
109
|
+
result = ensure_path(parse_text(Path, text))
|
110
|
+
assert result == result.expanduser()
|
111
|
+
|
112
|
+
def test_sentinel(self) -> None:
|
113
|
+
text = str(sentinel)
|
114
|
+
result = parse_text(Sentinel, text)
|
115
|
+
assert result is sentinel
|
116
|
+
|
117
|
+
@given(text=text_ascii())
|
118
|
+
def test_str(self, *, text: str) -> None:
|
119
|
+
result = parse_text(str, text)
|
120
|
+
assert result == text
|
121
|
+
|
122
|
+
@given(time=times())
|
123
|
+
def test_time(self, *, time: dt.time) -> None:
|
124
|
+
text = serialize_time(time)
|
125
|
+
result = parse_text(dt.time, text)
|
126
|
+
assert result == time
|
127
|
+
|
128
|
+
@given(timedelta=timedeltas_2w())
|
129
|
+
def test_timedelta(self, *, timedelta: dt.timedelta) -> None:
|
130
|
+
text = serialize_timedelta(timedelta)
|
131
|
+
result = parse_text(dt.timedelta, text)
|
132
|
+
assert result == timedelta
|
133
|
+
|
134
|
+
@given(version=versions())
|
135
|
+
def test_version(self, *, version: Version) -> None:
|
136
|
+
text = str(version)
|
137
|
+
result = parse_text(Version, text)
|
138
|
+
assert result == version
|
139
|
+
|
140
|
+
def test_error_bool(self) -> None:
|
141
|
+
with raises(
|
142
|
+
ParseTextError, match="Unable to parse <class 'bool'>; got 'invalid'"
|
143
|
+
):
|
144
|
+
_ = parse_text(bool, "invalid")
|
145
|
+
|
146
|
+
def test_error_date(self) -> None:
|
147
|
+
with raises(
|
148
|
+
ParseTextError,
|
149
|
+
match=r"Unable to parse <class 'datetime\.date'>; got 'invalid'",
|
150
|
+
):
|
151
|
+
_ = parse_text(dt.date, "invalid")
|
152
|
+
|
153
|
+
def test_error_datetime(self) -> None:
|
154
|
+
with raises(
|
155
|
+
ParseTextError,
|
156
|
+
match=r"Unable to parse <class 'datetime\.datetime'>; got 'invalid'",
|
157
|
+
):
|
158
|
+
_ = parse_text(dt.datetime, "invalid")
|
159
|
+
|
160
|
+
def test_error_enum(self) -> None:
|
161
|
+
with raises(
|
162
|
+
ParseTextError, match="Unable to parse <enum 'TruthEnum'>; got 'invalid'"
|
163
|
+
):
|
164
|
+
_ = parse_text(TruthEnum, "invalid")
|
165
|
+
|
166
|
+
def test_error_float(self) -> None:
|
167
|
+
with raises(
|
168
|
+
ParseTextError, match="Unable to parse <class 'float'>; got 'invalid'"
|
169
|
+
):
|
170
|
+
_ = parse_text(float, "invalid")
|
171
|
+
|
172
|
+
def test_error_int(self) -> None:
|
173
|
+
with raises(
|
174
|
+
ParseTextError, match="Unable to parse <class 'int'>; got 'invalid'"
|
175
|
+
):
|
176
|
+
_ = parse_text(int, "invalid")
|
177
|
+
|
178
|
+
def test_error_none(self) -> None:
|
179
|
+
with raises(ParseTextError, match="Unable to parse None; got 'invalid'"):
|
180
|
+
_ = parse_text(None, "invalid")
|
181
|
+
|
182
|
+
def test_error_none_type(self) -> None:
|
183
|
+
with raises(
|
184
|
+
ParseTextError, match="Unable to parse <class 'NoneType'>; got 'invalid'"
|
185
|
+
):
|
186
|
+
_ = parse_text(NoneType, "invalid")
|
187
|
+
|
188
|
+
def test_error_nullable_int(self) -> None:
|
189
|
+
with raises(
|
190
|
+
ParseTextError, match=r"Unable to parse int \| None; got 'invalid'"
|
191
|
+
):
|
192
|
+
_ = parse_text(int | None, "invalid")
|
193
|
+
|
194
|
+
def test_error_nullable_not_type(self) -> None:
|
195
|
+
with raises(
|
196
|
+
ParseTextError,
|
197
|
+
match=r"Unable to parse collections\.abc\.Iterable\[None\] \| None; got 'invalid'",
|
198
|
+
):
|
199
|
+
_ = parse_text(Iterable[None] | None, "invalid")
|
200
|
+
|
201
|
+
def test_error_sentinel(self) -> None:
|
202
|
+
with raises(
|
203
|
+
ParseTextError,
|
204
|
+
match=r"Unable to parse <class 'utilities\.sentinel\.Sentinel'>; got 'invalid'",
|
205
|
+
):
|
206
|
+
_ = parse_text(Sentinel, "invalid")
|
207
|
+
|
208
|
+
def test_error_time(self) -> None:
|
209
|
+
with raises(
|
210
|
+
ParseTextError,
|
211
|
+
match=r"Unable to parse <class 'datetime\.time'>; got 'invalid'",
|
212
|
+
):
|
213
|
+
_ = parse_text(dt.time, "invalid")
|
214
|
+
|
215
|
+
def test_error_timedelta(self) -> None:
|
216
|
+
with raises(
|
217
|
+
ParseTextError,
|
218
|
+
match=r"Unable to parse <class 'datetime\.timedelta'>; got 'invalid'",
|
219
|
+
):
|
220
|
+
_ = parse_text(dt.timedelta, "invalid")
|
221
|
+
|
222
|
+
def test_error_unknown_annotation(self) -> None:
|
223
|
+
with raises(ParseTextError, match=r"Unable to parse int \| str; got 'invalid'"):
|
224
|
+
_ = parse_text(int | str, "invalid")
|
225
|
+
|
226
|
+
def test_error_unknown_type(self) -> None:
|
227
|
+
@dataclass(kw_only=True)
|
228
|
+
class Example:
|
229
|
+
pass
|
230
|
+
|
231
|
+
with raises(
|
232
|
+
ParseTextError,
|
233
|
+
match=r"Unable to parse <class 'tests\.test_parse\.TestParseText\.test_error_unknown_type\.<locals>\.Example'>; got 'invalid'",
|
234
|
+
):
|
235
|
+
_ = parse_text(Example, "invalid")
|
236
|
+
|
237
|
+
def test_error_version(self) -> None:
|
238
|
+
with raises(
|
239
|
+
ParseTextError,
|
240
|
+
match=r"Unable to parse <class 'utilities\.version\.Version'>; got 'invalid'",
|
241
|
+
):
|
242
|
+
_ = parse_text(Version, "invalid")
|
@@ -0,0 +1,148 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import re
|
4
|
+
from dataclasses import dataclass
|
5
|
+
from re import DOTALL
|
6
|
+
from typing import TYPE_CHECKING
|
7
|
+
|
8
|
+
from hypothesis import given
|
9
|
+
from hypothesis.strategies import DataObject, booleans, data, integers, sampled_from
|
10
|
+
from pytest import raises
|
11
|
+
|
12
|
+
from utilities.errors import ImpossibleCaseError
|
13
|
+
from utilities.hypothesis import git_repos, settings_with_reduced_examples, text_ascii
|
14
|
+
from utilities.os import temp_environ
|
15
|
+
from utilities.python_dotenv import (
|
16
|
+
_LoadSettingsDuplicateKeysError,
|
17
|
+
_LoadSettingsEmptyError,
|
18
|
+
_LoadSettingsFileNotFoundError,
|
19
|
+
_LoadSettingsParseTextError,
|
20
|
+
load_settings,
|
21
|
+
)
|
22
|
+
|
23
|
+
if TYPE_CHECKING:
|
24
|
+
from pathlib import Path
|
25
|
+
|
26
|
+
|
27
|
+
class TestLoadSettings:
|
28
|
+
@given(
|
29
|
+
data=data(),
|
30
|
+
root=git_repos(),
|
31
|
+
key_file=sampled_from(["key", "KEY"]),
|
32
|
+
value_file=text_ascii(),
|
33
|
+
use_env=booleans(),
|
34
|
+
)
|
35
|
+
@settings_with_reduced_examples()
|
36
|
+
def test_main(
|
37
|
+
self,
|
38
|
+
*,
|
39
|
+
data: DataObject,
|
40
|
+
root: Path,
|
41
|
+
key_file: str,
|
42
|
+
value_file: str,
|
43
|
+
use_env: bool,
|
44
|
+
) -> None:
|
45
|
+
with root.joinpath(".env").open(mode="w") as fh:
|
46
|
+
_ = fh.write(f"{key_file} = {value_file}\n")
|
47
|
+
|
48
|
+
@dataclass(kw_only=True, slots=True)
|
49
|
+
class SettingsLower:
|
50
|
+
key: str
|
51
|
+
|
52
|
+
@dataclass(kw_only=True, slots=True)
|
53
|
+
class SettingsUpper:
|
54
|
+
KEY: str
|
55
|
+
|
56
|
+
SettingsUse = data.draw(sampled_from([SettingsLower, SettingsUpper])) # noqa: N806
|
57
|
+
if use_env:
|
58
|
+
key_env = data.draw(sampled_from(["key", "KEY"]))
|
59
|
+
value_env = data.draw(text_ascii())
|
60
|
+
with temp_environ({key_env: value_env}):
|
61
|
+
settings = load_settings(SettingsUse, cwd=root)
|
62
|
+
exp_value = value_env
|
63
|
+
else:
|
64
|
+
settings = load_settings(SettingsUse, cwd=root)
|
65
|
+
exp_value = value_file
|
66
|
+
|
67
|
+
if SettingsUse is SettingsLower:
|
68
|
+
expected = SettingsLower(key=exp_value)
|
69
|
+
elif SettingsUse is SettingsUpper:
|
70
|
+
expected = SettingsUpper(KEY=exp_value)
|
71
|
+
else:
|
72
|
+
raise ImpossibleCaseError(case=[f"{SettingsUse=}"])
|
73
|
+
assert settings == expected
|
74
|
+
|
75
|
+
@given(root=git_repos(), value=text_ascii())
|
76
|
+
@settings_with_reduced_examples()
|
77
|
+
def test_file_extra_key(self, *, root: Path, value: str) -> None:
|
78
|
+
@dataclass(kw_only=True, slots=True)
|
79
|
+
class Settings:
|
80
|
+
key: str
|
81
|
+
|
82
|
+
with root.joinpath(".env").open(mode="w") as fh:
|
83
|
+
_ = fh.write(f"key = {value}\n")
|
84
|
+
_ = fh.write(f"other = {value}\n")
|
85
|
+
|
86
|
+
settings = load_settings(Settings, cwd=root)
|
87
|
+
expected = Settings(key=value)
|
88
|
+
assert settings == expected
|
89
|
+
|
90
|
+
@given(root=git_repos())
|
91
|
+
@settings_with_reduced_examples()
|
92
|
+
def test_error_file_not_found(self, *, root: Path) -> None:
|
93
|
+
@dataclass(kw_only=True, slots=True)
|
94
|
+
class Settings:
|
95
|
+
KEY: str
|
96
|
+
|
97
|
+
with raises(_LoadSettingsFileNotFoundError, match=r"Path '.*' must exist"):
|
98
|
+
_ = load_settings(Settings, cwd=root)
|
99
|
+
|
100
|
+
@given(root=git_repos(), value=integers())
|
101
|
+
@settings_with_reduced_examples()
|
102
|
+
def test_error_duplicate_keys(self, *, root: Path, value: int) -> None:
|
103
|
+
@dataclass(kw_only=True, slots=True)
|
104
|
+
class Settings:
|
105
|
+
key: str
|
106
|
+
|
107
|
+
with root.joinpath(".env").open(mode="w") as fh:
|
108
|
+
_ = fh.write(f"key = {value}\n")
|
109
|
+
_ = fh.write(f"KEY = {value}\n")
|
110
|
+
|
111
|
+
with raises(
|
112
|
+
_LoadSettingsDuplicateKeysError,
|
113
|
+
match=re.compile(
|
114
|
+
r"Mapping .* keys must not contain duplicates \(modulo case\); got .*",
|
115
|
+
flags=DOTALL,
|
116
|
+
),
|
117
|
+
):
|
118
|
+
_ = load_settings(Settings, cwd=root)
|
119
|
+
|
120
|
+
@given(root=git_repos())
|
121
|
+
@settings_with_reduced_examples()
|
122
|
+
def test_error_field_missing(self, *, root: Path) -> None:
|
123
|
+
@dataclass(kw_only=True, slots=True)
|
124
|
+
class Settings:
|
125
|
+
key: str
|
126
|
+
|
127
|
+
root.joinpath(".env").touch()
|
128
|
+
|
129
|
+
with raises(
|
130
|
+
_LoadSettingsEmptyError, match=r"Field 'key' must exist \(modulo case\)"
|
131
|
+
):
|
132
|
+
_ = load_settings(Settings, cwd=root)
|
133
|
+
|
134
|
+
@given(root=git_repos())
|
135
|
+
@settings_with_reduced_examples()
|
136
|
+
def test_error_parse_text(self, *, root: Path) -> None:
|
137
|
+
@dataclass(kw_only=True, slots=True)
|
138
|
+
class Settings:
|
139
|
+
key: int
|
140
|
+
|
141
|
+
with root.joinpath(".env").open(mode="w") as fh:
|
142
|
+
_ = fh.write("key = '...'\n")
|
143
|
+
|
144
|
+
with raises(
|
145
|
+
_LoadSettingsParseTextError,
|
146
|
+
match=r"Unable to parse field 'key' of type <class 'int'>; got '...'",
|
147
|
+
):
|
148
|
+
_ = load_settings(Settings, cwd=root)
|
@@ -0,0 +1,45 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import TYPE_CHECKING
|
4
|
+
|
5
|
+
from hypothesis import given
|
6
|
+
from hypothesis.strategies import DataObject, data, sampled_from
|
7
|
+
from pytest import mark, param, raises
|
8
|
+
|
9
|
+
from utilities.sentinel import (
|
10
|
+
SENTINEL_REPR,
|
11
|
+
ParseSentinelError,
|
12
|
+
Sentinel,
|
13
|
+
parse_sentinel,
|
14
|
+
sentinel,
|
15
|
+
)
|
16
|
+
|
17
|
+
if TYPE_CHECKING:
|
18
|
+
from collections.abc import Callable
|
19
|
+
|
20
|
+
|
21
|
+
class TestParseSentinel:
|
22
|
+
@given(data=data())
|
23
|
+
def test_main(self, *, data: DataObject) -> None:
|
24
|
+
text = str(sentinel)
|
25
|
+
text_use = data.draw(sampled_from(["", text, text.lower(), text.upper()]))
|
26
|
+
result = parse_sentinel(text_use)
|
27
|
+
assert result is sentinel
|
28
|
+
|
29
|
+
def test_error(self) -> None:
|
30
|
+
with raises(
|
31
|
+
ParseSentinelError, match="Unable to parse sentinel value; got 'invalid'"
|
32
|
+
):
|
33
|
+
_ = parse_sentinel("invalid")
|
34
|
+
|
35
|
+
|
36
|
+
class TestSentinel:
|
37
|
+
def test_isinstance(self) -> None:
|
38
|
+
assert isinstance(sentinel, Sentinel)
|
39
|
+
|
40
|
+
@mark.parametrize("method", [param(repr), param(str)])
|
41
|
+
def test_repr_and_str(self, method: Callable[..., str]) -> None:
|
42
|
+
assert method(sentinel) == SENTINEL_REPR
|
43
|
+
|
44
|
+
def test_singletone(self) -> None:
|
45
|
+
assert Sentinel() is sentinel
|
@@ -16,8 +16,10 @@ from utilities.hypothesis import text_ascii
|
|
16
16
|
from utilities.sentinel import sentinel
|
17
17
|
from utilities.text import (
|
18
18
|
ParseBoolError,
|
19
|
+
ParseNoneError,
|
19
20
|
join_strs,
|
20
21
|
parse_bool,
|
22
|
+
parse_none,
|
21
23
|
repr_encode,
|
22
24
|
snake_case,
|
23
25
|
split_str,
|
@@ -29,18 +31,33 @@ from utilities.text import (
|
|
29
31
|
class TestParseBool:
|
30
32
|
@given(data=data(), value=booleans())
|
31
33
|
def test_main(self, *, data: DataObject, value: bool) -> None:
|
32
|
-
text =
|
33
|
-
|
34
|
-
|
34
|
+
text = str(value)
|
35
|
+
text_use = data.draw(
|
36
|
+
sampled_from([str(int(value)), text, text.lower(), text.upper()])
|
37
|
+
)
|
38
|
+
result = parse_bool(text_use)
|
35
39
|
assert result is value
|
36
40
|
|
37
41
|
def test_error(self) -> None:
|
38
42
|
with raises(
|
39
|
-
ParseBoolError, match="Unable to parse 'invalid'
|
43
|
+
ParseBoolError, match="Unable to parse boolean value; got 'invalid'"
|
40
44
|
):
|
41
45
|
_ = parse_bool("invalid")
|
42
46
|
|
43
47
|
|
48
|
+
class TestParseNone:
|
49
|
+
@given(data=data())
|
50
|
+
def test_main(self, *, data: DataObject) -> None:
|
51
|
+
text = str(None)
|
52
|
+
text_use = data.draw(sampled_from(["", text, text.lower(), text.upper()]))
|
53
|
+
result = parse_none(text_use)
|
54
|
+
assert result is None
|
55
|
+
|
56
|
+
def test_error(self) -> None:
|
57
|
+
with raises(ParseNoneError, match="Unable to parse null value; got 'invalid'"):
|
58
|
+
_ = parse_none("invalid")
|
59
|
+
|
60
|
+
|
44
61
|
class TestReprEncode:
|
45
62
|
@given(n=integers())
|
46
63
|
def test_main(self, *, n: int) -> None:
|
@@ -5,6 +5,7 @@ from collections.abc import Callable, Iterable, Iterator, Sequence
|
|
5
5
|
from dataclasses import asdict, dataclass, is_dataclass
|
6
6
|
from functools import _lru_cache_wrapper, cached_property, partial, reduce, wraps
|
7
7
|
from inspect import getattr_static
|
8
|
+
from pathlib import Path
|
8
9
|
from re import findall
|
9
10
|
from types import (
|
10
11
|
BuiltinFunctionType,
|
@@ -405,6 +406,31 @@ class EnsureNumberError(Exception):
|
|
405
406
|
##
|
406
407
|
|
407
408
|
|
409
|
+
@overload
|
410
|
+
def ensure_path(obj: Any, /, *, nullable: bool) -> Path | None: ...
|
411
|
+
@overload
|
412
|
+
def ensure_path(obj: Any, /, *, nullable: Literal[False] = False) -> Path: ...
|
413
|
+
def ensure_path(obj: Any, /, *, nullable: bool = False) -> Path | None:
|
414
|
+
"""Ensure an object is a Path."""
|
415
|
+
try:
|
416
|
+
return ensure_class(obj, Path, nullable=nullable)
|
417
|
+
except EnsureClassError as error:
|
418
|
+
raise EnsurePathError(obj=error.obj, nullable=nullable) from None
|
419
|
+
|
420
|
+
|
421
|
+
@dataclass(kw_only=True, slots=True)
|
422
|
+
class EnsurePathError(Exception):
|
423
|
+
obj: Any
|
424
|
+
nullable: bool
|
425
|
+
|
426
|
+
@override
|
427
|
+
def __str__(self) -> str:
|
428
|
+
return _make_error_msg(self.obj, "a Path", nullable=self.nullable)
|
429
|
+
|
430
|
+
|
431
|
+
##
|
432
|
+
|
433
|
+
|
408
434
|
def ensure_sized(obj: Any, /) -> Sized:
|
409
435
|
"""Ensure an object is sized."""
|
410
436
|
if is_sized(obj):
|
@@ -985,6 +1011,7 @@ __all__ = [
|
|
985
1011
|
"EnsureMemberError",
|
986
1012
|
"EnsureNotNoneError",
|
987
1013
|
"EnsureNumberError",
|
1014
|
+
"EnsurePathError",
|
988
1015
|
"EnsureSizedError",
|
989
1016
|
"EnsureSizedNotStrError",
|
990
1017
|
"EnsureStrError",
|
@@ -1004,6 +1031,7 @@ __all__ = [
|
|
1004
1031
|
"ensure_member",
|
1005
1032
|
"ensure_not_none",
|
1006
1033
|
"ensure_number",
|
1034
|
+
"ensure_path",
|
1007
1035
|
"ensure_sized",
|
1008
1036
|
"ensure_sized_not_str",
|
1009
1037
|
"ensure_str",
|