dycw-utilities 0.127.0__tar.gz → 0.128.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/PKG-INFO +1 -1
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/pyproject.toml +3 -2
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_asyncio.py +40 -23
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_asyncio_classes/loopers.py +2 -1
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_hypothesis.py +1 -15
- dycw_utilities-0.128.0/src/tests/test_pathlib.py +124 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_python_dotenv.py +6 -6
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/__init__.py +1 -1
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/datetime.py +0 -8
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/hypothesis.py +1 -11
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/logging.py +9 -12
- dycw_utilities-0.128.0/src/utilities/pathlib.py +114 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/pyinstrument.py +6 -4
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/pytest_regressions.py +2 -2
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/python_dotenv.py +10 -6
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/types.py +2 -2
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/version.py +0 -8
- dycw_utilities-0.127.0/src/tests/test_git.py +0 -71
- dycw_utilities-0.127.0/src/tests/test_pathlib.py +0 -60
- dycw_utilities-0.127.0/src/utilities/git.py +0 -93
- dycw_utilities-0.127.0/src/utilities/pathlib.py +0 -55
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/.gitignore +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/LICENSE +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/README.md +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/__init__.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/conftest.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/modules/__init__.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/modules/package_missing/__init__.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/modules/package_missing/module.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/modules/package_with/__init__.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/modules/package_with/outer_1.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/modules/package_with/outer_2.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/modules/package_with/subpackage/__init__.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/modules/package_with/subpackage/inner_1.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/modules/package_with/subpackage/inner_2.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/modules/package_with/subpackage/inner_3.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/modules/package_without/__init__.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/modules/package_without/module_1.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/modules/package_without/module_2.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/modules/standalone.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/modules/with_imports.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_altair.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_asyncio_classes/__init__.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_asyncio_classes/redis.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_atomicwrites.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_atools.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_cachetools.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_click.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_concurrent.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_contextlib.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_contextvars.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_cryptography.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_cvxpy.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_dataclasses.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_datetime.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_enum.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_errors.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_eventkit.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_fastapi.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_fpdf2.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_functions.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_functools.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_getpass.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_hashlib.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_http.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_importlib.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_ipython.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_iterables.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_jupyter.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_libcst.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_lightweight_charts.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_logging.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_loguru.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_luigi.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_math.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_memory_profiler.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_modules.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_more_itertools.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_numpy.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_operator.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_optuna.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_orjson.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_os.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_parse.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_period.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_pickle.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_platform.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_polars.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_polars_ols.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_pottery.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_pqdm.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_psutil.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_pydantic.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_pyinstrument.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_pyrsistent.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_pytest.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_pytest_regressions.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_random.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_re.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_redis.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_reprlib.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_scipy.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_sentinel.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_shelve.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_slack_sdk.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_socket.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_sqlalchemy.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_sqlalchemy_polars.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_statsmodel.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_streamlit.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_string.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_sys.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_tempfile.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_tenacity.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_text.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_threading.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_timer.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_traceback.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_traceback_funcs/__init__.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_traceback_funcs/chain.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_traceback_funcs/decorated_async.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_traceback_funcs/decorated_sync.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_traceback_funcs/error_bind.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_traceback_funcs/many.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_traceback_funcs/one.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_traceback_funcs/recursive.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_traceback_funcs/task_group_one.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_traceback_funcs/task_group_two.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_traceback_funcs/two.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_traceback_funcs/untraced.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_types.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_typing.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_typing_funcs/__init__.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_typing_funcs/no_future.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_typing_funcs/with_future.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_tzdata.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_tzlocal.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_uuid.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_version.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_warnings.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_whenever.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_zipfile.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/tests/test_zoneinfo.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/altair.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/asyncio.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/atomicwrites.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/atools.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/cachetools.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/click.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/concurrent.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/contextlib.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/contextvars.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/cryptography.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/cvxpy.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/dataclasses.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/enum.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/errors.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/eventkit.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/fastapi.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/fpdf2.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/functions.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/functools.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/getpass.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/hashlib.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/http.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/importlib.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/ipython.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/iterables.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/jupyter.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/libcst.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/lightweight_charts.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/loguru.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/luigi.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/math.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/memory_profiler.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/modules.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/more_itertools.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/numpy.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/operator.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/optuna.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/orjson.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/os.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/parse.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/period.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/pickle.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/platform.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/polars.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/polars_ols.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/pottery.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/pqdm.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/psutil.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/py.typed +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/pydantic.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/pyrsistent.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/pytest.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/random.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/re.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/redis.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/reprlib.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/scipy.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/sentinel.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/shelve.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/slack_sdk.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/socket.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/sqlalchemy.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/sqlalchemy_polars.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/statsmodels.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/streamlit.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/string.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/sys.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/tempfile.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/tenacity.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/text.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/threading.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/timer.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/traceback.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/typing.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/tzdata.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/tzlocal.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/uuid.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/warnings.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/whenever.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/zipfile.py +0 -0
- {dycw_utilities-0.127.0 → dycw_utilities-0.128.0}/src/utilities/zoneinfo.py +0 -0
@@ -94,7 +94,7 @@ dependencies = [
|
|
94
94
|
name = "dycw-utilities"
|
95
95
|
readme = "README.md"
|
96
96
|
requires-python = ">= 3.12"
|
97
|
-
version = "0.
|
97
|
+
version = "0.128.0"
|
98
98
|
|
99
99
|
[project.optional-dependencies]
|
100
100
|
test = [
|
@@ -334,7 +334,7 @@ zzz-test-zoneinfo = [
|
|
334
334
|
# bump-my-version
|
335
335
|
[tool.bumpversion]
|
336
336
|
allow_dirty = true
|
337
|
-
current_version = "0.
|
337
|
+
current_version = "0.128.0"
|
338
338
|
|
339
339
|
[[tool.bumpversion.files]]
|
340
340
|
filename = "src/utilities/__init__.py"
|
@@ -447,6 +447,7 @@ filterwarnings = [
|
|
447
447
|
"ignore:Task .* without outputs has no custom complete.* method:UserWarning", # luigi
|
448
448
|
"ignore:There is no current event loop:DeprecationWarning", # eventkit
|
449
449
|
"ignore:Using fork.* can cause Polars to deadlock in the child process:RuntimeWarning", # polars/pqdm
|
450
|
+
"ignore:coroutine 'AsyncConnection.close' was never awaited:RuntimeWarning",
|
450
451
|
"ignore:loop is closed:ResourceWarning", # redis
|
451
452
|
"ignore:unclosed <StreamWriter .*>:ResourceWarning", # redis
|
452
453
|
"ignore:unclosed <socket.*socket .*>:ResourceWarning", # redis
|
@@ -15,7 +15,6 @@ from hypothesis.strategies import (
|
|
15
15
|
booleans,
|
16
16
|
data,
|
17
17
|
integers,
|
18
|
-
just,
|
19
18
|
lists,
|
20
19
|
none,
|
21
20
|
permutations,
|
@@ -71,6 +70,7 @@ from utilities.hypothesis import sentinels, text_ascii
|
|
71
70
|
from utilities.iterables import one, unique_everseen
|
72
71
|
from utilities.pytest import skipif_windows
|
73
72
|
from utilities.sentinel import Sentinel, sentinel
|
73
|
+
from utilities.text import unique_str
|
74
74
|
from utilities.timer import Timer
|
75
75
|
|
76
76
|
if TYPE_CHECKING:
|
@@ -198,7 +198,6 @@ class TestEnhancedTaskGroup:
|
|
198
198
|
assert looper.running
|
199
199
|
assert not looper.running
|
200
200
|
|
201
|
-
@mark.flaky
|
202
201
|
async def test_max_tasks_disabled(self) -> None:
|
203
202
|
with Timer() as timer:
|
204
203
|
async with EnhancedTaskGroup() as tg:
|
@@ -625,7 +624,7 @@ class TestInfiniteLooper:
|
|
625
624
|
):
|
626
625
|
raise _InfiniteLooperDefaultEventError(looper=looper)
|
627
626
|
|
628
|
-
@given(
|
627
|
+
@given(log=booleans())
|
629
628
|
@mark.parametrize(("sleep_restart", "desc"), sleep_restart_cases)
|
630
629
|
@settings(suppress_health_check={HealthCheck.function_scoped_fixture})
|
631
630
|
async def test_error_upon_initialize(
|
@@ -633,7 +632,7 @@ class TestInfiniteLooper:
|
|
633
632
|
*,
|
634
633
|
sleep_restart: DurationOrEveryDuration,
|
635
634
|
desc: str,
|
636
|
-
|
635
|
+
log: bool,
|
637
636
|
caplog: LogCaptureFixture,
|
638
637
|
) -> None:
|
639
638
|
class CustomError(Exception): ...
|
@@ -650,15 +649,19 @@ class TestInfiniteLooper:
|
|
650
649
|
|
651
650
|
async with (
|
652
651
|
timeout(1.0),
|
653
|
-
Example(
|
652
|
+
Example(
|
653
|
+
sleep_core=0.1,
|
654
|
+
sleep_restart=sleep_restart,
|
655
|
+
logger=unique_str() if log else None,
|
656
|
+
),
|
654
657
|
):
|
655
658
|
...
|
656
|
-
if
|
659
|
+
if log:
|
657
660
|
message = caplog.messages[0]
|
658
661
|
expected = f"'Example' encountered 'CustomError()' whilst initializing; sleeping {desc}..."
|
659
662
|
assert message == expected
|
660
663
|
|
661
|
-
@given(
|
664
|
+
@given(log=booleans())
|
662
665
|
@mark.parametrize(("sleep_restart", "desc"), sleep_restart_cases)
|
663
666
|
@settings(suppress_health_check={HealthCheck.function_scoped_fixture})
|
664
667
|
async def test_error_upon_core(
|
@@ -666,7 +669,7 @@ class TestInfiniteLooper:
|
|
666
669
|
*,
|
667
670
|
sleep_restart: DurationOrEveryDuration,
|
668
671
|
desc: str,
|
669
|
-
|
672
|
+
log: bool,
|
670
673
|
caplog: LogCaptureFixture,
|
671
674
|
) -> None:
|
672
675
|
class CustomError(Exception): ...
|
@@ -679,15 +682,19 @@ class TestInfiniteLooper:
|
|
679
682
|
|
680
683
|
async with (
|
681
684
|
timeout(1.0),
|
682
|
-
Example(
|
685
|
+
Example(
|
686
|
+
sleep_core=0.1,
|
687
|
+
sleep_restart=sleep_restart,
|
688
|
+
logger=unique_str() if log else None,
|
689
|
+
),
|
683
690
|
):
|
684
691
|
...
|
685
|
-
if
|
692
|
+
if log:
|
686
693
|
message = caplog.messages[0]
|
687
694
|
expected = f"'Example' encountered 'CustomError()'; sleeping {desc}..."
|
688
695
|
assert message == expected
|
689
696
|
|
690
|
-
@given(
|
697
|
+
@given(log=booleans())
|
691
698
|
@mark.parametrize(("sleep_restart", "desc"), sleep_restart_cases)
|
692
699
|
@settings(suppress_health_check={HealthCheck.function_scoped_fixture})
|
693
700
|
async def test_error_upon_teardown(
|
@@ -695,7 +702,7 @@ class TestInfiniteLooper:
|
|
695
702
|
*,
|
696
703
|
sleep_restart: DurationOrEveryDuration,
|
697
704
|
desc: str,
|
698
|
-
|
705
|
+
log: bool,
|
699
706
|
caplog: LogCaptureFixture,
|
700
707
|
) -> None:
|
701
708
|
class Custom1Error(Exception): ...
|
@@ -724,14 +731,18 @@ class TestInfiniteLooper:
|
|
724
731
|
|
725
732
|
async with (
|
726
733
|
timeout(1.0),
|
727
|
-
Example(
|
734
|
+
Example(
|
735
|
+
sleep_core=0.1,
|
736
|
+
sleep_restart=sleep_restart,
|
737
|
+
logger=unique_str() if log else None,
|
738
|
+
),
|
728
739
|
):
|
729
740
|
...
|
730
|
-
if
|
741
|
+
if log:
|
731
742
|
expected = f"'Example' encountered 'Custom2Error()' whilst tearing down; sleeping {desc}..."
|
732
743
|
assert expected in caplog.messages
|
733
744
|
|
734
|
-
@given(
|
745
|
+
@given(log=booleans())
|
735
746
|
@mark.parametrize(("sleep_restart", "desc"), sleep_restart_cases)
|
736
747
|
@settings(suppress_health_check={HealthCheck.function_scoped_fixture})
|
737
748
|
async def test_error_group_upon_others(
|
@@ -739,7 +750,7 @@ class TestInfiniteLooper:
|
|
739
750
|
*,
|
740
751
|
sleep_restart: DurationOrEveryDuration,
|
741
752
|
desc: str,
|
742
|
-
|
753
|
+
log: bool,
|
743
754
|
caplog: LogCaptureFixture,
|
744
755
|
) -> None:
|
745
756
|
class CustomError(Exception): ...
|
@@ -770,10 +781,14 @@ class TestInfiniteLooper:
|
|
770
781
|
|
771
782
|
async with (
|
772
783
|
timeout(1.0),
|
773
|
-
Example(
|
784
|
+
Example(
|
785
|
+
sleep_core=0.05,
|
786
|
+
sleep_restart=sleep_restart,
|
787
|
+
logger=unique_str() if log else None,
|
788
|
+
),
|
774
789
|
):
|
775
790
|
...
|
776
|
-
if
|
791
|
+
if log:
|
777
792
|
message = caplog.messages[0]
|
778
793
|
expected = f"""\
|
779
794
|
'Example' encountered 1 error(s):
|
@@ -867,11 +882,10 @@ class TestInfiniteQueueLooper:
|
|
867
882
|
await looper.run_until_empty(stop=True)
|
868
883
|
assert looper.empty()
|
869
884
|
|
870
|
-
@given(
|
871
|
-
@mark.flaky
|
885
|
+
@given(log=booleans())
|
872
886
|
@settings(suppress_health_check={HealthCheck.function_scoped_fixture})
|
873
887
|
async def test_error_process_items(
|
874
|
-
self, *,
|
888
|
+
self, *, log: bool, caplog: LogCaptureFixture
|
875
889
|
) -> None:
|
876
890
|
class CustomError(Exception): ...
|
877
891
|
|
@@ -883,9 +897,12 @@ class TestInfiniteQueueLooper:
|
|
883
897
|
async def _process_queue(self) -> None:
|
884
898
|
raise CustomError
|
885
899
|
|
886
|
-
async with
|
900
|
+
async with (
|
901
|
+
timeout(1.0),
|
902
|
+
Example(sleep_core=0.05, logger=unique_str() if log else None) as looper,
|
903
|
+
):
|
887
904
|
looper.put_left_nowait(1)
|
888
|
-
if
|
905
|
+
if log:
|
889
906
|
message = caplog.messages[0]
|
890
907
|
expected = "'Example' encountered 'CustomError()'; sleeping for 0:01:00..."
|
891
908
|
assert message == expected
|
@@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, Any, Literal, override
|
|
5
5
|
|
6
6
|
from pytest import approx
|
7
7
|
|
8
|
+
from tests.conftest import IS_CI
|
8
9
|
from utilities.asyncio import Looper
|
9
10
|
from utilities.contextlib import suppress_super_object_attribute_error
|
10
11
|
from utilities.datetime import MILLISECOND
|
@@ -16,7 +17,7 @@ if TYPE_CHECKING:
|
|
16
17
|
|
17
18
|
_FREQ: Duration = 10 * MILLISECOND
|
18
19
|
_BACKOFF: Duration = 100 * MILLISECOND
|
19
|
-
_REL: float = 0.
|
20
|
+
_REL: float = 2.0 if IS_CI else 0.25
|
20
21
|
|
21
22
|
|
22
23
|
# assert
|
@@ -4,7 +4,6 @@ import datetime as dt
|
|
4
4
|
from itertools import pairwise
|
5
5
|
from pathlib import Path
|
6
6
|
from re import search
|
7
|
-
from subprocess import PIPE, check_output
|
8
7
|
from typing import TYPE_CHECKING, Any, cast
|
9
8
|
|
10
9
|
from hypothesis import HealthCheck, Phase, assume, given, settings
|
@@ -45,7 +44,6 @@ from utilities.datetime import (
|
|
45
44
|
parse_two_digit_year,
|
46
45
|
)
|
47
46
|
from utilities.functions import ensure_int
|
48
|
-
from utilities.git import _GIT_REMOTE_GET_URL_ORIGIN, _GIT_REV_PARSE_ABBREV_REV_HEAD
|
49
47
|
from utilities.hypothesis import (
|
50
48
|
_SQLALCHEMY_ENGINE_DIALECTS,
|
51
49
|
MaybeSearchStrategy,
|
@@ -590,21 +588,9 @@ class TestGitRepos:
|
|
590
588
|
@given(data=data())
|
591
589
|
@settings_with_reduced_examples()
|
592
590
|
def test_main(self, *, data: DataObject) -> None:
|
593
|
-
|
594
|
-
remote = data.draw(text_ascii(min_size=1) | none())
|
595
|
-
root = data.draw(git_repos(branch=branch, remote=remote))
|
591
|
+
root = data.draw(git_repos())
|
596
592
|
files = set(root.iterdir())
|
597
593
|
assert Path(root, ".git") in files
|
598
|
-
if branch is not None:
|
599
|
-
output = check_output(
|
600
|
-
_GIT_REV_PARSE_ABBREV_REV_HEAD, stderr=PIPE, cwd=root, text=True
|
601
|
-
)
|
602
|
-
assert output.strip("\n") == branch
|
603
|
-
if remote is not None:
|
604
|
-
output = check_output(
|
605
|
-
_GIT_REMOTE_GET_URL_ORIGIN, stderr=PIPE, cwd=root, text=True
|
606
|
-
)
|
607
|
-
assert output.strip("\n") == remote
|
608
594
|
|
609
595
|
|
610
596
|
class TestHashables:
|
@@ -0,0 +1,124 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from dataclasses import dataclass, field
|
4
|
+
from pathlib import Path
|
5
|
+
from typing import TYPE_CHECKING, Self
|
6
|
+
|
7
|
+
from hypothesis import given, settings
|
8
|
+
from hypothesis.strategies import integers, sets
|
9
|
+
from pytest import mark, param, raises
|
10
|
+
|
11
|
+
from utilities.dataclasses import replace_non_sentinel
|
12
|
+
from utilities.hypothesis import git_repos, paths, temp_paths
|
13
|
+
from utilities.pathlib import (
|
14
|
+
GetRootError,
|
15
|
+
ensure_suffix,
|
16
|
+
get_path,
|
17
|
+
get_root,
|
18
|
+
list_dir,
|
19
|
+
temp_cwd,
|
20
|
+
)
|
21
|
+
from utilities.sentinel import Sentinel, sentinel
|
22
|
+
|
23
|
+
if TYPE_CHECKING:
|
24
|
+
from utilities.types import MaybeCallablePathLike
|
25
|
+
|
26
|
+
|
27
|
+
class TestEnsureSuffix:
|
28
|
+
@mark.parametrize(
|
29
|
+
("path", "suffix", "expected"),
|
30
|
+
[
|
31
|
+
param("foo", ".txt", "foo.txt"),
|
32
|
+
param("foo.txt", ".txt", "foo.txt"),
|
33
|
+
param("foo.bar.baz", ".baz", "foo.bar.baz"),
|
34
|
+
param("foo.bar.baz", ".quux", "foo.bar.baz.quux"),
|
35
|
+
],
|
36
|
+
ids=str,
|
37
|
+
)
|
38
|
+
def test_main(self, *, path: Path, suffix: str, expected: str) -> None:
|
39
|
+
result = str(ensure_suffix(path, suffix))
|
40
|
+
assert result == expected
|
41
|
+
|
42
|
+
|
43
|
+
class TestGetPath:
|
44
|
+
@given(path=paths())
|
45
|
+
def test_path(self, *, path: Path) -> None:
|
46
|
+
assert get_path(path=path) == path
|
47
|
+
|
48
|
+
@given(path=paths())
|
49
|
+
def test_str(self, *, path: Path) -> None:
|
50
|
+
assert get_path(path=str(path)) == path
|
51
|
+
|
52
|
+
def test_none(self) -> None:
|
53
|
+
assert get_path(path=None) == Path.cwd()
|
54
|
+
|
55
|
+
def test_sentinel(self) -> None:
|
56
|
+
assert get_path(path=sentinel) is sentinel
|
57
|
+
|
58
|
+
@given(path1=paths(), path2=paths())
|
59
|
+
def test_replace_non_sentinel(self, *, path1: Path, path2: Path) -> None:
|
60
|
+
@dataclass(kw_only=True, slots=True)
|
61
|
+
class Example:
|
62
|
+
path: Path = field(default_factory=Path.cwd)
|
63
|
+
|
64
|
+
def replace(
|
65
|
+
self, *, path: MaybeCallablePathLike | Sentinel = sentinel
|
66
|
+
) -> Self:
|
67
|
+
return replace_non_sentinel(self, path=get_path(path=path))
|
68
|
+
|
69
|
+
obj = Example(path=path1)
|
70
|
+
assert obj.path == path1
|
71
|
+
assert obj.replace().path == path1
|
72
|
+
assert obj.replace(path=path2).path == path2
|
73
|
+
|
74
|
+
@given(path=paths())
|
75
|
+
def test_callable(self, *, path: Path) -> None:
|
76
|
+
assert get_path(path=lambda: path) == path
|
77
|
+
|
78
|
+
|
79
|
+
class TestGetRoot:
|
80
|
+
@given(repo=git_repos())
|
81
|
+
@settings(max_examples=1)
|
82
|
+
def test_git(self, *, repo: Path) -> None:
|
83
|
+
root = get_root(path=repo)
|
84
|
+
expected = repo.resolve()
|
85
|
+
assert root == expected
|
86
|
+
|
87
|
+
@given(root=temp_paths())
|
88
|
+
@settings(max_examples=1)
|
89
|
+
def test_envrc(self, *, root: Path) -> None:
|
90
|
+
root.joinpath(".envrc").touch()
|
91
|
+
result = get_root(path=root)
|
92
|
+
assert result == root
|
93
|
+
|
94
|
+
@given(root=temp_paths())
|
95
|
+
@settings(max_examples=1)
|
96
|
+
def test_envrc_from_inside(self, *, root: Path) -> None:
|
97
|
+
root.joinpath(".envrc").touch()
|
98
|
+
path = root.joinpath("foo", "bar", "baz")
|
99
|
+
path.mkdir(parents=True)
|
100
|
+
result = get_root(path=path)
|
101
|
+
assert result == root
|
102
|
+
|
103
|
+
def test_error(self, *, tmp_path: Path) -> None:
|
104
|
+
with raises(GetRootError, match="Unable to determine root from '.*'"):
|
105
|
+
_ = get_root(path=tmp_path)
|
106
|
+
|
107
|
+
|
108
|
+
class TestListDir:
|
109
|
+
@given(root=temp_paths(), nums=sets(integers(0, 100), max_size=10))
|
110
|
+
def test_main(self, *, root: Path, nums: set[str]) -> None:
|
111
|
+
for n in nums:
|
112
|
+
path = root.joinpath(f"{n}.txt")
|
113
|
+
path.touch()
|
114
|
+
result = list_dir(root)
|
115
|
+
expected = sorted(Path(root, f"{n}.txt") for n in nums)
|
116
|
+
assert result == expected
|
117
|
+
|
118
|
+
|
119
|
+
class TestTempCWD:
|
120
|
+
def test_main(self, *, tmp_path: Path) -> None:
|
121
|
+
assert Path.cwd() != tmp_path
|
122
|
+
with temp_cwd(tmp_path):
|
123
|
+
assert Path.cwd() == tmp_path
|
124
|
+
assert Path.cwd() != tmp_path
|
@@ -57,10 +57,10 @@ class TestLoadSettings:
|
|
57
57
|
key_env = data.draw(sampled_from(["key", "KEY"]))
|
58
58
|
value_env = data.draw(text_ascii())
|
59
59
|
with temp_environ({key_env: value_env}):
|
60
|
-
settings = load_settings(SettingsUse,
|
60
|
+
settings = load_settings(SettingsUse, path=root)
|
61
61
|
exp_value = value_env
|
62
62
|
else:
|
63
|
-
settings = load_settings(SettingsUse,
|
63
|
+
settings = load_settings(SettingsUse, path=root)
|
64
64
|
exp_value = value_file
|
65
65
|
|
66
66
|
if SettingsUse is SettingsLower:
|
@@ -82,7 +82,7 @@ class TestLoadSettings:
|
|
82
82
|
_ = fh.write(f"key = {value}\n")
|
83
83
|
_ = fh.write(f"other = {value}\n")
|
84
84
|
|
85
|
-
settings = load_settings(Settings,
|
85
|
+
settings = load_settings(Settings, path=root)
|
86
86
|
expected = Settings(key=value)
|
87
87
|
assert settings == expected
|
88
88
|
|
@@ -94,7 +94,7 @@ class TestLoadSettings:
|
|
94
94
|
KEY: str
|
95
95
|
|
96
96
|
with raises(_LoadSettingsFileNotFoundError, match=r"Path '.*' must exist"):
|
97
|
-
_ = load_settings(Settings,
|
97
|
+
_ = load_settings(Settings, path=root)
|
98
98
|
|
99
99
|
@given(root=git_repos(), value=integers())
|
100
100
|
@settings_with_reduced_examples()
|
@@ -114,7 +114,7 @@ class TestLoadSettings:
|
|
114
114
|
flags=DOTALL,
|
115
115
|
),
|
116
116
|
):
|
117
|
-
_ = load_settings(Settings,
|
117
|
+
_ = load_settings(Settings, path=root)
|
118
118
|
|
119
119
|
@given(root=git_repos())
|
120
120
|
@settings_with_reduced_examples()
|
@@ -129,4 +129,4 @@ class TestLoadSettings:
|
|
129
129
|
_LoadSettingsMissingKeysError,
|
130
130
|
match=r"Unable to load '.*'; missing value\(s\) for 'key'",
|
131
131
|
):
|
132
|
-
_ = load_settings(Settings,
|
132
|
+
_ = load_settings(Settings, path=root)
|
@@ -509,14 +509,6 @@ def get_datetime(*, datetime: MaybeCallableDateTime) -> dt.datetime: ...
|
|
509
509
|
def get_datetime(*, datetime: None) -> None: ...
|
510
510
|
@overload
|
511
511
|
def get_datetime(*, datetime: Sentinel) -> Sentinel: ...
|
512
|
-
@overload
|
513
|
-
def get_datetime(
|
514
|
-
*, datetime: MaybeCallableDateTime | Sentinel
|
515
|
-
) -> dt.datetime | Sentinel: ...
|
516
|
-
@overload
|
517
|
-
def get_datetime(
|
518
|
-
*, datetime: MaybeCallableDateTime | None | Sentinel = sentinel
|
519
|
-
) -> dt.datetime | None | Sentinel: ...
|
520
512
|
def get_datetime(
|
521
513
|
*, datetime: MaybeCallableDateTime | None | Sentinel = sentinel
|
522
514
|
) -> dt.datetime | None | Sentinel:
|
@@ -506,13 +506,7 @@ def floats_extra(
|
|
506
506
|
|
507
507
|
|
508
508
|
@composite
|
509
|
-
def git_repos(
|
510
|
-
draw: DrawFn,
|
511
|
-
/,
|
512
|
-
*,
|
513
|
-
branch: MaybeSearchStrategy[str | None] = None,
|
514
|
-
remote: MaybeSearchStrategy[str | None] = None,
|
515
|
-
) -> Path:
|
509
|
+
def git_repos(draw: DrawFn, /) -> Path:
|
516
510
|
path = draw(temp_paths())
|
517
511
|
with temp_cwd(path):
|
518
512
|
_ = check_call(["git", "init", "-b", "master"])
|
@@ -525,10 +519,6 @@ def git_repos(
|
|
525
519
|
_ = check_call(["git", "commit", "-m", "add"])
|
526
520
|
_ = check_call(["git", "rm", file_str])
|
527
521
|
_ = check_call(["git", "commit", "-m", "rm"])
|
528
|
-
if (branch_ := draw2(draw, branch)) is not None:
|
529
|
-
_ = check_call(["git", "checkout", "-b", branch_])
|
530
|
-
if (remote_ := draw2(draw, remote)) is not None:
|
531
|
-
_ = check_call(["git", "remote", "add", "origin", remote_])
|
532
522
|
return path
|
533
523
|
|
534
524
|
|
@@ -46,9 +46,8 @@ from utilities.datetime import (
|
|
46
46
|
serialize_compact,
|
47
47
|
)
|
48
48
|
from utilities.errors import ImpossibleCaseError
|
49
|
-
from utilities.git import get_repo_root
|
50
49
|
from utilities.iterables import OneEmptyError, always_iterable, one
|
51
|
-
from utilities.pathlib import ensure_suffix,
|
50
|
+
from utilities.pathlib import ensure_suffix, get_path, get_root
|
52
51
|
from utilities.reprlib import (
|
53
52
|
RICH_EXPAND_ALL,
|
54
53
|
RICH_INDENT_SIZE,
|
@@ -68,9 +67,9 @@ if TYPE_CHECKING:
|
|
68
67
|
from utilities.types import (
|
69
68
|
LoggerOrName,
|
70
69
|
LogLevel,
|
70
|
+
MaybeCallablePathLike,
|
71
71
|
MaybeIterable,
|
72
72
|
PathLike,
|
73
|
-
PathLikeOrCallable,
|
74
73
|
)
|
75
74
|
from utilities.version import MaybeCallableVersionLike
|
76
75
|
|
@@ -383,10 +382,10 @@ class StandaloneFileHandler(Handler):
|
|
383
382
|
|
384
383
|
@override
|
385
384
|
def __init__(
|
386
|
-
self, *, level: int = NOTSET, path:
|
385
|
+
self, *, level: int = NOTSET, path: MaybeCallablePathLike | None = None
|
387
386
|
) -> None:
|
388
387
|
super().__init__(level=level)
|
389
|
-
self._path = path
|
388
|
+
self._path = get_path(path=path)
|
390
389
|
|
391
390
|
@override
|
392
391
|
def emit(self, record: LogRecord) -> None:
|
@@ -394,10 +393,8 @@ class StandaloneFileHandler(Handler):
|
|
394
393
|
from utilities.tzlocal import get_now_local
|
395
394
|
|
396
395
|
try:
|
397
|
-
path = (
|
398
|
-
|
399
|
-
.joinpath(serialize_compact(get_now_local()))
|
400
|
-
.with_suffix(".txt")
|
396
|
+
path = self._path.joinpath(serialize_compact(get_now_local())).with_suffix(
|
397
|
+
".txt"
|
401
398
|
)
|
402
399
|
formatted = self.format(record)
|
403
400
|
with writer(path, overwrite=True) as temp, temp.open(mode="w") as fh:
|
@@ -473,7 +470,7 @@ class FilterForKeyError(Exception):
|
|
473
470
|
|
474
471
|
def get_default_logging_path() -> Path:
|
475
472
|
"""Get the logging default path."""
|
476
|
-
return
|
473
|
+
return get_root().joinpath(".logs")
|
477
474
|
|
478
475
|
|
479
476
|
##
|
@@ -520,7 +517,7 @@ def setup_logging(
|
|
520
517
|
console_level: LogLevel | None = "INFO",
|
521
518
|
console_filters: Iterable[_FilterType] | None = None,
|
522
519
|
console_fmt: str = "❯ {_zoned_datetime_str} | {name}:{funcName}:{lineno} | {message}", # noqa: RUF001
|
523
|
-
files_dir:
|
520
|
+
files_dir: MaybeCallablePathLike | None = get_default_logging_path,
|
524
521
|
files_when: _When = "D",
|
525
522
|
files_interval: int = 1,
|
526
523
|
files_backup_count: int = 10,
|
@@ -616,7 +613,7 @@ def setup_logging(
|
|
616
613
|
logger_use.addHandler(console_high_and_exc_handler)
|
617
614
|
|
618
615
|
# debug & info
|
619
|
-
directory =
|
616
|
+
directory = get_path(path=files_dir) # skipif-ci-and-windows
|
620
617
|
levels: list[LogLevel] = ["DEBUG", "INFO"] # skipif-ci-and-windows
|
621
618
|
for level, (subpath, files_or_plain_formatter) in product( # skipif-ci-and-windows
|
622
619
|
levels, [(Path(), files_formatter), (Path("plain"), plain_formatter)]
|
@@ -0,0 +1,114 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from collections.abc import Callable
|
4
|
+
from contextlib import contextmanager, suppress
|
5
|
+
from dataclasses import dataclass
|
6
|
+
from itertools import chain
|
7
|
+
from os import chdir
|
8
|
+
from pathlib import Path
|
9
|
+
from re import IGNORECASE, search
|
10
|
+
from subprocess import PIPE, CalledProcessError, check_output
|
11
|
+
from typing import TYPE_CHECKING, assert_never, overload, override
|
12
|
+
|
13
|
+
from utilities.sentinel import Sentinel, sentinel
|
14
|
+
|
15
|
+
if TYPE_CHECKING:
|
16
|
+
from collections.abc import Iterator, Sequence
|
17
|
+
|
18
|
+
from utilities.types import MaybeCallablePathLike, PathLike
|
19
|
+
|
20
|
+
PWD = Path.cwd()
|
21
|
+
|
22
|
+
|
23
|
+
def ensure_suffix(path: PathLike, suffix: str, /) -> Path:
|
24
|
+
"""Ensure a path has a given suffix."""
|
25
|
+
path = Path(path)
|
26
|
+
parts = path.name.split(".")
|
27
|
+
parts = list(chain([parts[0]], (f".{p}" for p in parts[1:])))
|
28
|
+
if (len(parts) == 0) or (parts[-1] != suffix):
|
29
|
+
parts.append(suffix)
|
30
|
+
name = "".join(parts)
|
31
|
+
return path.with_name(name)
|
32
|
+
|
33
|
+
|
34
|
+
##
|
35
|
+
|
36
|
+
|
37
|
+
@overload
|
38
|
+
def get_path(*, path: MaybeCallablePathLike | None) -> Path: ...
|
39
|
+
@overload
|
40
|
+
def get_path(*, path: Sentinel) -> Sentinel: ...
|
41
|
+
def get_path(
|
42
|
+
*, path: MaybeCallablePathLike | None | Sentinel = sentinel
|
43
|
+
) -> Path | None | Sentinel:
|
44
|
+
"""Get the path."""
|
45
|
+
match path:
|
46
|
+
case Path() | Sentinel():
|
47
|
+
return path
|
48
|
+
case str():
|
49
|
+
return Path(path)
|
50
|
+
case None:
|
51
|
+
return Path.cwd()
|
52
|
+
case Callable() as func:
|
53
|
+
return get_path(path=func())
|
54
|
+
case _ as never:
|
55
|
+
assert_never(never)
|
56
|
+
|
57
|
+
|
58
|
+
##
|
59
|
+
|
60
|
+
|
61
|
+
def get_root(*, path: MaybeCallablePathLike | None = None) -> Path:
|
62
|
+
"""Get the root of a path."""
|
63
|
+
path = get_path(path=path)
|
64
|
+
try:
|
65
|
+
output = check_output(
|
66
|
+
["git", "rev-parse", "--show-toplevel"], stderr=PIPE, cwd=path, text=True
|
67
|
+
)
|
68
|
+
except CalledProcessError as error:
|
69
|
+
# newer versions of git report "Not a git repository", whilst older
|
70
|
+
# versions report "not a git repository"
|
71
|
+
if not search("fatal: not a git repository", error.stderr, flags=IGNORECASE):
|
72
|
+
raise # pragma: no cover
|
73
|
+
else:
|
74
|
+
return Path(output.strip("\n"))
|
75
|
+
all_paths = list(chain([path], path.parents))
|
76
|
+
with suppress(StopIteration):
|
77
|
+
return next(
|
78
|
+
p for p in all_paths if any(p_i.name == ".envrc" for p_i in p.iterdir())
|
79
|
+
)
|
80
|
+
raise GetRootError(path=path)
|
81
|
+
|
82
|
+
|
83
|
+
@dataclass(kw_only=True, slots=True)
|
84
|
+
class GetRootError(Exception):
|
85
|
+
path: PathLike
|
86
|
+
|
87
|
+
@override
|
88
|
+
def __str__(self) -> str:
|
89
|
+
return f"Unable to determine root from {str(self.path)!r}"
|
90
|
+
|
91
|
+
|
92
|
+
##
|
93
|
+
|
94
|
+
|
95
|
+
def list_dir(path: PathLike, /) -> Sequence[Path]:
|
96
|
+
"""List the contents of a directory."""
|
97
|
+
return sorted(Path(path).iterdir())
|
98
|
+
|
99
|
+
|
100
|
+
##
|
101
|
+
|
102
|
+
|
103
|
+
@contextmanager
|
104
|
+
def temp_cwd(path: PathLike, /) -> Iterator[None]:
|
105
|
+
"""Context manager with temporary current working directory set."""
|
106
|
+
prev = Path.cwd()
|
107
|
+
chdir(path)
|
108
|
+
try:
|
109
|
+
yield
|
110
|
+
finally:
|
111
|
+
chdir(prev)
|
112
|
+
|
113
|
+
|
114
|
+
__all__ = ["PWD", "ensure_suffix", "get_path", "list_dir", "temp_cwd"]
|