dycw-utilities 0.153.14__tar.gz → 0.154.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.153.14 → dycw_utilities-0.154.0}/PKG-INFO +1 -1
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/pyproject.toml +2 -2
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/conftest.py +2 -8
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_iterables.py +0 -14
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_logging.py +8 -47
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_math.py +3 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_sqlalchemy.py +2 -2
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_traceback.py +16 -3
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_whenever.py +6 -3
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/__init__.py +1 -1
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/fpdf2.py +2 -2
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/iterables.py +1 -21
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/logging.py +8 -49
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/polars.py +2 -1
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/pyinstrument.py +2 -4
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/traceback.py +24 -16
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/whenever.py +8 -4
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/.gitignore +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/LICENSE +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/README.md +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/__init__.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/modules/__init__.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/modules/package_missing/__init__.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/modules/package_missing/module.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/modules/package_with/__init__.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/modules/package_with/outer_1.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/modules/package_with/outer_2.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/modules/package_with/subpackage/__init__.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/modules/package_with/subpackage/inner_1.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/modules/package_with/subpackage/inner_2.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/modules/package_with/subpackage/inner_3.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/modules/package_without/__init__.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/modules/package_without/module_1.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/modules/package_without/module_2.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/modules/standalone.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/modules/with_imports.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_altair.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_asyncio.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_atomicwrites.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_atools.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_cachetools.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_click.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_concurrent.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_contextlib.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_contextvars.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_cryptography.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_cvxpy.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_dataclasses.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_enum.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_errors.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_eventkit.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_fastapi.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_fpdf2.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_functions.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_functools.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_getpass.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_gzip.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_hashlib.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_http.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_hypothesis.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_importlib.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_inflect.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_ipython.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_json.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_jupyter.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_libcst.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_lightweight_charts.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_memory_profiler.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_modules.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_more_itertools.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_numpy.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_objects/__init__.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_objects/objects.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_operator.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_optuna.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_orjson.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_os.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_parse.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_pathlib.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_pickle.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_platform.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_polars.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_polars_ols.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_postgres.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_pottery.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_pqdm.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_psutil.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_pyinstrument.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_pytest.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_pytest_randomly.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_pytest_regressions.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_random.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_re.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_redis.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_reprlib.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_scipy.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_sentinel.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_shelve.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_slack_sdk.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_socket.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_sqlalchemy_polars.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_statsmodels.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_string.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_tempfile.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_text.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_threading.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_timer.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_typed_settings.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_types.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_typing.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_typing_funcs/__init__.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_typing_funcs/no_future.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_typing_funcs/with_future.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_tzdata.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_tzlocal.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_uuid.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_version.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_warnings.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_yield_access/__init__.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_yield_access/script.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_yield_access/script.sh +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_zipfile.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/tests/test_zoneinfo.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/altair.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/asyncio.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/atomicwrites.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/atools.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/cachetools.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/click.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/concurrent.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/contextlib.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/contextvars.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/cryptography.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/cvxpy.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/dataclasses.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/enum.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/errors.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/eventkit.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/fastapi.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/functions.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/functools.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/getpass.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/gzip.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/hashlib.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/http.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/hypothesis.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/importlib.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/inflect.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/ipython.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/json.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/jupyter.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/libcst.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/lightweight_charts.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/math.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/memory_profiler.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/modules.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/more_itertools.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/numpy.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/operator.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/optuna.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/orjson.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/os.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/parse.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/pathlib.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/pickle.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/platform.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/polars_ols.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/postgres.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/pottery.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/pqdm.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/psutil.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/py.typed +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/pytest.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/pytest_plugins/__init__.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/pytest_plugins/pytest_randomly.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/pytest_plugins/pytest_regressions.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/pytest_regressions.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/random.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/re.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/redis.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/reprlib.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/scipy.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/sentinel.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/shelve.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/slack_sdk.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/socket.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/sqlalchemy.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/sqlalchemy_polars.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/statsmodels.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/string.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/tempfile.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/text.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/threading.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/timer.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/typed_settings.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/types.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/typing.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/tzdata.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/tzlocal.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/uuid.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/version.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/warnings.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/zipfile.py +0 -0
- {dycw_utilities-0.153.14 → dycw_utilities-0.154.0}/src/utilities/zoneinfo.py +0 -0
@@ -229,7 +229,7 @@ dependencies = [
|
|
229
229
|
name = "dycw-utilities"
|
230
230
|
readme = "README.md"
|
231
231
|
requires-python = ">= 3.12"
|
232
|
-
version = "0.
|
232
|
+
version = "0.154.0"
|
233
233
|
|
234
234
|
[project.entry-points.pytest11]
|
235
235
|
pytest-randomly = "utilities.pytest_plugins.pytest_randomly"
|
@@ -262,7 +262,7 @@ test = [
|
|
262
262
|
# bump-my-version
|
263
263
|
[tool.bumpversion]
|
264
264
|
allow_dirty = true
|
265
|
-
current_version = "0.
|
265
|
+
current_version = "0.154.0"
|
266
266
|
|
267
267
|
[[tool.bumpversion.files]]
|
268
268
|
filename = "src/utilities/__init__.py"
|
@@ -7,13 +7,11 @@ from typing import TYPE_CHECKING
|
|
7
7
|
|
8
8
|
from hypothesis import HealthCheck
|
9
9
|
from pytest import fixture, mark, param, skip
|
10
|
-
from whenever import PlainDateTime
|
11
10
|
|
12
11
|
from utilities.contextlib import enhanced_context_manager
|
13
12
|
from utilities.platform import IS_MAC, IS_NOT_LINUX, IS_WINDOWS
|
14
13
|
from utilities.re import ExtractGroupError, extract_group
|
15
|
-
from utilities.
|
16
|
-
from utilities.whenever import MINUTE, get_now
|
14
|
+
from utilities.whenever import MINUTE, get_now, parse_plain_local
|
17
15
|
|
18
16
|
if TYPE_CHECKING:
|
19
17
|
from collections.abc import AsyncIterator, Iterator, Sequence
|
@@ -174,11 +172,7 @@ def _is_to_drop(table: str, /) -> bool:
|
|
174
172
|
datetime_str = extract_group(r"^(\d{8}T\d{6})_", table)
|
175
173
|
except ExtractGroupError:
|
176
174
|
return True
|
177
|
-
|
178
|
-
LOCAL_TIME_ZONE_NAME
|
179
|
-
)
|
180
|
-
now = get_now()
|
181
|
-
return (now - datetime) >= MINUTE
|
175
|
+
return (get_now() - parse_plain_local(datetime_str)) >= MINUTE
|
182
176
|
|
183
177
|
|
184
178
|
def _select_tables() -> TextClause:
|
@@ -94,7 +94,6 @@ from utilities.iterables import (
|
|
94
94
|
enumerate_with_edge,
|
95
95
|
expanding_window,
|
96
96
|
filter_include_and_exclude,
|
97
|
-
group_consecutive_integers,
|
98
97
|
groupby_lists,
|
99
98
|
hashable_to_iterable,
|
100
99
|
is_iterable,
|
@@ -117,7 +116,6 @@ from utilities.iterables import (
|
|
117
116
|
sum_mappings,
|
118
117
|
take,
|
119
118
|
transpose,
|
120
|
-
ungroup_consecutive_integers,
|
121
119
|
unique_everseen,
|
122
120
|
)
|
123
121
|
from utilities.sentinel import Sentinel, sentinel
|
@@ -745,18 +743,6 @@ class TestFilterIncludeAndExclude:
|
|
745
743
|
assert result == expected
|
746
744
|
|
747
745
|
|
748
|
-
class TestGroupAndUngroupConsecutiveIntegers:
|
749
|
-
@given(xs=lists(integers(), unique=True).map(sorted))
|
750
|
-
def test_main(self, *, xs: list[int]) -> None:
|
751
|
-
result = list(ungroup_consecutive_integers(group_consecutive_integers(xs)))
|
752
|
-
assert result == xs
|
753
|
-
|
754
|
-
def test_example(self) -> None:
|
755
|
-
result = list(group_consecutive_integers([1, 2, 3, 6, 7, 10, 11, 12]))
|
756
|
-
expected = [(1, 3), (6, 7), (10, 12)]
|
757
|
-
assert result == expected
|
758
|
-
|
759
|
-
|
760
746
|
class TestGroupbyLists:
|
761
747
|
iterable: ClassVar[str] = "AAAABBBCCDAABB"
|
762
748
|
|
@@ -8,20 +8,13 @@ from re import search
|
|
8
8
|
from typing import TYPE_CHECKING, Any, cast
|
9
9
|
|
10
10
|
from hypothesis import given
|
11
|
-
from hypothesis.strategies import booleans, integers
|
11
|
+
from hypothesis.strategies import booleans, integers
|
12
12
|
from pytest import LogCaptureFixture, mark, param, raises
|
13
13
|
|
14
14
|
from tests.conftest import SKIPIF_CI_AND_WINDOWS
|
15
|
-
from utilities.hypothesis import
|
16
|
-
assume_does_not_raise,
|
17
|
-
pairs,
|
18
|
-
temp_paths,
|
19
|
-
text_ascii,
|
20
|
-
zoned_datetimes,
|
21
|
-
)
|
15
|
+
from utilities.hypothesis import pairs, temp_paths, text_ascii, zoned_datetimes
|
22
16
|
from utilities.iterables import one
|
23
17
|
from utilities.logging import (
|
24
|
-
FilterForKeyError,
|
25
18
|
GetLoggingLevelNumberError,
|
26
19
|
SizeAndTimeRotatingFileHandler,
|
27
20
|
_compute_rollover_actions,
|
@@ -29,7 +22,6 @@ from utilities.logging import (
|
|
29
22
|
_RotatingLogFile,
|
30
23
|
add_filters,
|
31
24
|
basic_config,
|
32
|
-
filter_for_key,
|
33
25
|
get_format_str,
|
34
26
|
get_formatter,
|
35
27
|
get_logging_level_number,
|
@@ -39,7 +31,7 @@ from utilities.logging import (
|
|
39
31
|
from utilities.text import unique_str
|
40
32
|
from utilities.types import LogLevel
|
41
33
|
from utilities.typing import get_args
|
42
|
-
from utilities.whenever import
|
34
|
+
from utilities.whenever import get_now, to_local_plain
|
43
35
|
|
44
36
|
if TYPE_CHECKING:
|
45
37
|
from collections.abc import Mapping
|
@@ -196,7 +188,7 @@ class TestComputeRolloverActions:
|
|
196
188
|
|
197
189
|
await sleep(1)
|
198
190
|
tmp_path.joinpath("log.txt").touch()
|
199
|
-
now =
|
191
|
+
now = to_local_plain(get_now())
|
200
192
|
tmp_path.joinpath(f"log.99__{now}__{now}.txt").touch()
|
201
193
|
actions = _compute_rollover_actions(tmp_path, "log", ".txt")
|
202
194
|
assert len(actions.deletions) == 2
|
@@ -209,33 +201,6 @@ class TestComputeRolloverActions:
|
|
209
201
|
)
|
210
202
|
|
211
203
|
|
212
|
-
class TestFilterForKey:
|
213
|
-
@given(key=text_ascii(), value=booleans() | none(), default=booleans())
|
214
|
-
def test_main(self, *, key: str, value: bool | None, default: bool) -> None:
|
215
|
-
logger = getLogger(unique_str())
|
216
|
-
logger.addHandler(handler := StreamHandler(buffer := StringIO()))
|
217
|
-
with assume_does_not_raise(FilterForKeyError):
|
218
|
-
filter_ = filter_for_key(key, default=default)
|
219
|
-
add_filters(handler, filter_)
|
220
|
-
match value:
|
221
|
-
case bool():
|
222
|
-
logger.warning("message", extra={key: value})
|
223
|
-
expected = value
|
224
|
-
case None:
|
225
|
-
logger.warning("message")
|
226
|
-
expected = default
|
227
|
-
result = buffer.getvalue() != ""
|
228
|
-
assert result is expected
|
229
|
-
|
230
|
-
def test_sunder(self) -> None:
|
231
|
-
_ = filter_for_key("_key")
|
232
|
-
|
233
|
-
@given(key=sampled_from(["msg", "__dunder__"]))
|
234
|
-
def test_error(self, *, key: str) -> None:
|
235
|
-
with raises(FilterForKeyError, match="Invalid key: '.*'"):
|
236
|
-
_ = filter_for_key(key)
|
237
|
-
|
238
|
-
|
239
204
|
class TestGetFormatStr:
|
240
205
|
@mark.parametrize("prefix", [param(">"), param(None)])
|
241
206
|
@mark.parametrize("hostname", [param(True), param(False)])
|
@@ -313,7 +278,7 @@ class TestRotatingLogFile:
|
|
313
278
|
def test_from_path_with_index_and_end(
|
314
279
|
self, *, index: int, end: ZonedDateTime
|
315
280
|
) -> None:
|
316
|
-
path = Path(f"log.{index}__{
|
281
|
+
path = Path(f"log.{index}__{to_local_plain(end)}.txt")
|
317
282
|
result = _RotatingLogFile.from_path(path, "log", ".txt")
|
318
283
|
assert result is not None
|
319
284
|
assert result.stem == "log"
|
@@ -327,9 +292,7 @@ class TestRotatingLogFile:
|
|
327
292
|
self, *, index: int, datetimes: tuple[ZonedDateTime, ZonedDateTime]
|
328
293
|
) -> None:
|
329
294
|
start, end = datetimes
|
330
|
-
path = Path(
|
331
|
-
f"log.{index}__{format_compact(to_local_plain(start))}__{format_compact(to_local_plain(end))}.txt"
|
332
|
-
)
|
295
|
+
path = Path(f"log.{index}__{to_local_plain(start)}__{to_local_plain(end)}.txt")
|
333
296
|
result = _RotatingLogFile.from_path(path, "log", ".txt")
|
334
297
|
assert result is not None
|
335
298
|
assert result.stem == "log"
|
@@ -359,9 +322,7 @@ class TestRotatingLogFile:
|
|
359
322
|
file = _RotatingLogFile(
|
360
323
|
directory=root, stem="log", suffix=".txt", index=index, end=end
|
361
324
|
)
|
362
|
-
assert file.path == root.joinpath(
|
363
|
-
f"log.{index}__{format_compact(to_local_plain(end))}.txt"
|
364
|
-
)
|
325
|
+
assert file.path == root.joinpath(f"log.{index}__{to_local_plain(end)}.txt")
|
365
326
|
|
366
327
|
@given(
|
367
328
|
root=temp_paths(),
|
@@ -376,7 +337,7 @@ class TestRotatingLogFile:
|
|
376
337
|
directory=root, stem="log", suffix=".txt", index=index, start=start, end=end
|
377
338
|
)
|
378
339
|
assert file.path == root.joinpath(
|
379
|
-
f"log.{index}__{
|
340
|
+
f"log.{index}__{to_local_plain(start)}__{to_local_plain(end)}.txt"
|
380
341
|
)
|
381
342
|
|
382
343
|
|
@@ -1175,6 +1175,9 @@ class TestRoundToFloat:
|
|
1175
1175
|
param(1.8, 0.5, 2.0),
|
1176
1176
|
param(1.9, 0.5, 2.0),
|
1177
1177
|
param(2.0, 0.5, 2.0),
|
1178
|
+
param(1.0745999813079834, 5e-5, 1.0746),
|
1179
|
+
param(1.0745500326156616, 5e-5, 1.07455),
|
1180
|
+
param(1.0745999813079834, 5e-5, 1.0746),
|
1178
1181
|
]
|
1179
1182
|
|
1180
1183
|
@mark.parametrize(("x", "y", "expected"), cases)
|
@@ -99,7 +99,7 @@ from utilities.sqlalchemy import (
|
|
99
99
|
)
|
100
100
|
from utilities.text import strip_and_dedent
|
101
101
|
from utilities.typing import get_args, get_literal_elements
|
102
|
-
from utilities.whenever import MILLISECOND,
|
102
|
+
from utilities.whenever import MILLISECOND, get_now, to_local_plain
|
103
103
|
|
104
104
|
if TYPE_CHECKING:
|
105
105
|
from collections.abc import Callable, Iterator
|
@@ -111,7 +111,7 @@ if TYPE_CHECKING:
|
|
111
111
|
def _table_names() -> str:
|
112
112
|
"""Generate at unique string."""
|
113
113
|
key = str(uuid4()).replace("-", "")
|
114
|
-
return f"{
|
114
|
+
return f"{to_local_plain(get_now())}_{key}"
|
115
115
|
|
116
116
|
|
117
117
|
@overload
|
@@ -8,20 +8,24 @@ from typing import TYPE_CHECKING
|
|
8
8
|
|
9
9
|
from hypothesis import given
|
10
10
|
from hypothesis.strategies import sampled_from
|
11
|
-
from pytest import CaptureFixture, raises
|
11
|
+
from pytest import CaptureFixture, mark, param, raises
|
12
12
|
|
13
13
|
from utilities.iterables import one
|
14
14
|
from utilities.traceback import (
|
15
15
|
MakeExceptHookError,
|
16
|
+
_make_except_hook_purge,
|
16
17
|
_path_to_dots,
|
17
18
|
format_exception_stack,
|
18
19
|
make_except_hook,
|
19
20
|
)
|
20
21
|
from utilities.tzlocal import LOCAL_TIME_ZONE_NAME
|
22
|
+
from utilities.whenever import SECOND, get_now, to_local_plain
|
21
23
|
|
22
24
|
if TYPE_CHECKING:
|
23
25
|
from collections.abc import Iterable
|
24
26
|
|
27
|
+
from utilities.types import Delta
|
28
|
+
|
25
29
|
|
26
30
|
class TestFormatExceptionStack:
|
27
31
|
@classmethod
|
@@ -93,8 +97,9 @@ class TestMakeExceptHook:
|
|
93
97
|
hook(exc_type, exc_val, traceback)
|
94
98
|
assert capsys.readouterr() != ""
|
95
99
|
|
96
|
-
|
97
|
-
|
100
|
+
@mark.parametrize("path_max_age", [param(SECOND), param(None)])
|
101
|
+
def test_path(self, *, tmp_path: Path, path_max_age: Delta | None) -> None:
|
102
|
+
hook = make_except_hook(path=tmp_path, path_max_age=path_max_age)
|
98
103
|
try:
|
99
104
|
_ = 1 / 0
|
100
105
|
except ZeroDivisionError:
|
@@ -109,6 +114,14 @@ class TestMakeExceptHook:
|
|
109
114
|
with raises(MakeExceptHookError, match="No exception to log"):
|
110
115
|
hook(exc_type, exc_val, traceback)
|
111
116
|
|
117
|
+
def test_purge(self, *, tmp_path: Path) -> None:
|
118
|
+
now = get_now()
|
119
|
+
path = tmp_path.joinpath(to_local_plain(now - 2 * SECOND)).with_suffix(".txt")
|
120
|
+
path.touch()
|
121
|
+
assert len(list(tmp_path.iterdir())) == 1
|
122
|
+
_make_except_hook_purge(tmp_path, SECOND)
|
123
|
+
assert len(list(tmp_path.iterdir())) == 0
|
124
|
+
|
112
125
|
|
113
126
|
class TestPathToDots:
|
114
127
|
@given(
|
@@ -122,6 +122,7 @@ from utilities.whenever import (
|
|
122
122
|
get_today_local,
|
123
123
|
mean_datetime,
|
124
124
|
min_max_date,
|
125
|
+
parse_plain_local,
|
125
126
|
round_date_or_date_time,
|
126
127
|
sub_year_month,
|
127
128
|
to_date,
|
@@ -1016,11 +1017,13 @@ class TestToHours:
|
|
1016
1017
|
_ = to_hours(delta)
|
1017
1018
|
|
1018
1019
|
|
1019
|
-
class
|
1020
|
+
class TestToLocalPlainAndParsePlainLocal:
|
1020
1021
|
@given(date_time=zoned_datetimes())
|
1021
1022
|
def test_main(self, *, date_time: ZonedDateTime) -> None:
|
1022
|
-
|
1023
|
-
assert isinstance(
|
1023
|
+
text = to_local_plain(date_time)
|
1024
|
+
assert isinstance(text, str)
|
1025
|
+
parsed = parse_plain_local(text)
|
1026
|
+
assert abs(parsed - date_time) <= SECOND
|
1024
1027
|
|
1025
1028
|
|
1026
1029
|
class TestToMicroseconds:
|
@@ -6,7 +6,7 @@ from typing import TYPE_CHECKING, override
|
|
6
6
|
from fpdf import FPDF
|
7
7
|
from fpdf.enums import XPos, YPos
|
8
8
|
|
9
|
-
from utilities.whenever import
|
9
|
+
from utilities.whenever import get_now, to_local_plain
|
10
10
|
|
11
11
|
if TYPE_CHECKING:
|
12
12
|
from collections.abc import Iterator
|
@@ -47,7 +47,7 @@ def yield_pdf(*, header: str | None = None) -> Iterator[_BasePDF]:
|
|
47
47
|
def footer(self) -> None:
|
48
48
|
self.set_y(-15)
|
49
49
|
self.set_font(family="Helvetica", style="I", size=8)
|
50
|
-
page_no, now = (self.page_no(),
|
50
|
+
page_no, now = (self.page_no(), to_local_plain(get_now()))
|
51
51
|
text = f"page {page_no}/{{}}; {now}"
|
52
52
|
_ = self.cell(
|
53
53
|
w=0,
|
@@ -18,7 +18,7 @@ from enum import Enum
|
|
18
18
|
from functools import cmp_to_key, partial, reduce
|
19
19
|
from itertools import accumulate, chain, groupby, islice, pairwise, product
|
20
20
|
from math import isnan
|
21
|
-
from operator import add,
|
21
|
+
from operator import add, or_
|
22
22
|
from typing import (
|
23
23
|
TYPE_CHECKING,
|
24
24
|
Any,
|
@@ -821,24 +821,6 @@ def filter_include_and_exclude[T, U](
|
|
821
821
|
##
|
822
822
|
|
823
823
|
|
824
|
-
def group_consecutive_integers(iterable: Iterable[int], /) -> Iterable[tuple[int, int]]:
|
825
|
-
"""Group consecutive integers."""
|
826
|
-
integers = sorted(iterable)
|
827
|
-
for _, group in groupby(enumerate(integers), key=lambda x: x[1] - x[0]):
|
828
|
-
as_list = list(map(itemgetter(1), group))
|
829
|
-
yield as_list[0], as_list[-1]
|
830
|
-
|
831
|
-
|
832
|
-
def ungroup_consecutive_integers(
|
833
|
-
iterable: Iterable[tuple[int, int]], /
|
834
|
-
) -> Iterable[int]:
|
835
|
-
"""Ungroup consecutive integers."""
|
836
|
-
return chain.from_iterable(range(start, end + 1) for start, end in iterable)
|
837
|
-
|
838
|
-
|
839
|
-
##
|
840
|
-
|
841
|
-
|
842
824
|
@overload
|
843
825
|
def groupby_lists[T](
|
844
826
|
iterable: Iterable[T], /, *, key: None = None
|
@@ -1504,7 +1486,6 @@ __all__ = [
|
|
1504
1486
|
"enumerate_with_edge",
|
1505
1487
|
"expanding_window",
|
1506
1488
|
"filter_include_and_exclude",
|
1507
|
-
"group_consecutive_integers",
|
1508
1489
|
"groupby_lists",
|
1509
1490
|
"hashable_to_iterable",
|
1510
1491
|
"is_iterable",
|
@@ -1527,6 +1508,5 @@ __all__ = [
|
|
1527
1508
|
"sum_mappings",
|
1528
1509
|
"take",
|
1529
1510
|
"transpose",
|
1530
|
-
"ungroup_consecutive_integers",
|
1531
1511
|
"unique_everseen",
|
1532
1512
|
]
|
@@ -31,7 +31,7 @@ from typing import (
|
|
31
31
|
override,
|
32
32
|
)
|
33
33
|
|
34
|
-
from whenever import
|
34
|
+
from whenever import ZonedDateTime
|
35
35
|
|
36
36
|
from utilities.atomicwrites import move_many
|
37
37
|
from utilities.dataclasses import replace_non_sentinel
|
@@ -45,16 +45,15 @@ from utilities.re import (
|
|
45
45
|
extract_groups,
|
46
46
|
)
|
47
47
|
from utilities.sentinel import Sentinel, sentinel
|
48
|
-
from utilities.tzlocal import LOCAL_TIME_ZONE_NAME
|
49
48
|
from utilities.whenever import (
|
50
49
|
WheneverLogRecord,
|
51
|
-
format_compact,
|
52
50
|
get_now_local,
|
51
|
+
parse_plain_local,
|
53
52
|
to_local_plain,
|
54
53
|
)
|
55
54
|
|
56
55
|
if TYPE_CHECKING:
|
57
|
-
from collections.abc import
|
56
|
+
from collections.abc import Iterable, Mapping
|
58
57
|
from datetime import time
|
59
58
|
from logging import _FilterType
|
60
59
|
|
@@ -151,42 +150,6 @@ def basic_config(
|
|
151
150
|
##
|
152
151
|
|
153
152
|
|
154
|
-
def filter_for_key(
|
155
|
-
key: str, /, *, default: bool = False
|
156
|
-
) -> Callable[[LogRecord], bool]:
|
157
|
-
"""Make a filter for a given attribute."""
|
158
|
-
if (key in _FILTER_FOR_KEY_BLACKLIST) or key.startswith("__"):
|
159
|
-
raise FilterForKeyError(key=key)
|
160
|
-
|
161
|
-
def filter_(record: LogRecord, /) -> bool:
|
162
|
-
try:
|
163
|
-
value = getattr(record, key)
|
164
|
-
except AttributeError:
|
165
|
-
return default
|
166
|
-
return bool(value)
|
167
|
-
|
168
|
-
return filter_
|
169
|
-
|
170
|
-
|
171
|
-
# fmt: off
|
172
|
-
_FILTER_FOR_KEY_BLACKLIST = {
|
173
|
-
"args", "created", "exc_info", "exc_text", "filename", "funcName", "getMessage", "levelname", "levelno", "lineno", "module", "msecs", "msg", "name", "pathname", "process", "processName", "relativeCreated", "stack_info", "taskName", "thread", "threadName"
|
174
|
-
}
|
175
|
-
# fmt: on
|
176
|
-
|
177
|
-
|
178
|
-
@dataclass(kw_only=True, slots=True)
|
179
|
-
class FilterForKeyError(Exception):
|
180
|
-
key: str
|
181
|
-
|
182
|
-
@override
|
183
|
-
def __str__(self) -> str:
|
184
|
-
return f"Invalid key: {self.key!r}"
|
185
|
-
|
186
|
-
|
187
|
-
##
|
188
|
-
|
189
|
-
|
190
153
|
def get_format_str(*, prefix: str | None = None, hostname: bool = False) -> str:
|
191
154
|
"""Generate a format string."""
|
192
155
|
parts: list[str] = [
|
@@ -535,10 +498,8 @@ class _RotatingLogFile:
|
|
535
498
|
stem=stem,
|
536
499
|
suffix=suffix,
|
537
500
|
index=int(index),
|
538
|
-
start=
|
539
|
-
|
540
|
-
),
|
541
|
-
end=PlainDateTime.parse_common_iso(end).assume_tz(LOCAL_TIME_ZONE_NAME),
|
501
|
+
start=parse_plain_local(start),
|
502
|
+
end=parse_plain_local(end),
|
542
503
|
)
|
543
504
|
try:
|
544
505
|
index, end = extract_groups(patterns.pattern2, path.name)
|
@@ -550,7 +511,7 @@ class _RotatingLogFile:
|
|
550
511
|
stem=stem,
|
551
512
|
suffix=suffix,
|
552
513
|
index=int(index),
|
553
|
-
end=
|
514
|
+
end=parse_plain_local(end),
|
554
515
|
)
|
555
516
|
try:
|
556
517
|
index = extract_group(patterns.pattern1, path.name)
|
@@ -571,9 +532,9 @@ class _RotatingLogFile:
|
|
571
532
|
case int() as index, None, None:
|
572
533
|
tail = str(index)
|
573
534
|
case int() as index, None, ZonedDateTime() as end:
|
574
|
-
tail = f"{index}__{
|
535
|
+
tail = f"{index}__{to_local_plain(end)}"
|
575
536
|
case int() as index, ZonedDateTime() as start, ZonedDateTime() as end:
|
576
|
-
tail = f"{index}__{
|
537
|
+
tail = f"{index}__{to_local_plain(start)}__{to_local_plain(end)}"
|
577
538
|
case _: # pragma: no cover
|
578
539
|
raise ImpossibleCaseError(
|
579
540
|
case=[f"{self.index=}", f"{self.start=}", f"{self.end=}"]
|
@@ -626,12 +587,10 @@ def to_logger(logger: LoggerLike | None = None, /) -> Logger:
|
|
626
587
|
|
627
588
|
|
628
589
|
__all__ = [
|
629
|
-
"FilterForKeyError",
|
630
590
|
"GetLoggingLevelNumberError",
|
631
591
|
"SizeAndTimeRotatingFileHandler",
|
632
592
|
"add_filters",
|
633
593
|
"basic_config",
|
634
|
-
"filter_for_key",
|
635
594
|
"get_format_str",
|
636
595
|
"get_logging_level_number",
|
637
596
|
"setup_logging",
|
@@ -2383,7 +2383,8 @@ def round_to_float(
|
|
2383
2383
|
) -> ExprOrSeries:
|
2384
2384
|
"""Round a column to the nearest multiple of another float."""
|
2385
2385
|
x = ensure_expr_or_series(x)
|
2386
|
-
|
2386
|
+
z = (x / y).round(mode=mode) * y
|
2387
|
+
return z.round(decimals=number_of_decimals(y) + 1)
|
2387
2388
|
|
2388
2389
|
|
2389
2390
|
##
|
@@ -8,7 +8,7 @@ from pyinstrument.profiler import Profiler
|
|
8
8
|
|
9
9
|
from utilities.atomicwrites import writer
|
10
10
|
from utilities.pathlib import to_path
|
11
|
-
from utilities.whenever import
|
11
|
+
from utilities.whenever import get_now, to_local_plain
|
12
12
|
|
13
13
|
if TYPE_CHECKING:
|
14
14
|
from collections.abc import Iterator
|
@@ -21,9 +21,7 @@ def profile(path: MaybeCallablePathLike = Path.cwd, /) -> Iterator[None]:
|
|
21
21
|
"""Profile the contents of a block."""
|
22
22
|
with Profiler() as profiler:
|
23
23
|
yield
|
24
|
-
filename = to_path(path).joinpath(
|
25
|
-
f"profile__{format_compact(to_local_plain(get_now()))}.html"
|
26
|
-
)
|
24
|
+
filename = to_path(path).joinpath(f"profile__{to_local_plain(get_now())}.html")
|
27
25
|
with writer(filename) as temp:
|
28
26
|
_ = temp.write_text(profiler.output_html())
|
29
27
|
|
@@ -33,6 +33,7 @@ from utilities.whenever import (
|
|
33
33
|
format_compact,
|
34
34
|
get_now,
|
35
35
|
get_now_local,
|
36
|
+
parse_plain_local,
|
36
37
|
to_local_plain,
|
37
38
|
to_zoned_date_time,
|
38
39
|
)
|
@@ -43,6 +44,7 @@ if TYPE_CHECKING:
|
|
43
44
|
from types import TracebackType
|
44
45
|
|
45
46
|
from utilities.types import (
|
47
|
+
Delta,
|
46
48
|
MaybeCallableBoolLike,
|
47
49
|
MaybeCallablePathLike,
|
48
50
|
MaybeCallableZonedDateTimeLike,
|
@@ -95,16 +97,10 @@ def _yield_header_lines(
|
|
95
97
|
) -> Iterator[str]:
|
96
98
|
"""Yield the header lines."""
|
97
99
|
now = get_now_local()
|
98
|
-
start_use = to_zoned_date_time(start)
|
99
100
|
yield f"Date/time | {format_compact(now)}"
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
start_str = format_compact(start_use.to_tz(LOCAL_TIME_ZONE_NAME))
|
104
|
-
yield f"Started | {start_str}"
|
105
|
-
delta = None if start_use is None else (now - start_use)
|
106
|
-
delta_str = "" if delta is None else delta.format_common_iso()
|
107
|
-
yield f"Duration | {delta_str}"
|
101
|
+
start_use = to_zoned_date_time(start).to_tz(LOCAL_TIME_ZONE_NAME)
|
102
|
+
yield f"Started | {format_compact(start_use)}"
|
103
|
+
yield f"Duration | {(now - start_use).format_common_iso()}"
|
108
104
|
yield f"User | {getuser()}"
|
109
105
|
yield f"Host | {gethostname()}"
|
110
106
|
yield f"Process ID | {getpid()}"
|
@@ -205,6 +201,7 @@ def make_except_hook(
|
|
205
201
|
start: MaybeCallableZonedDateTimeLike = get_now,
|
206
202
|
version: MaybeCallableVersionLike | None = None,
|
207
203
|
path: MaybeCallablePathLike | None = None,
|
204
|
+
path_max_age: Delta | None = None,
|
208
205
|
max_width: int = RICH_MAX_WIDTH,
|
209
206
|
indent_size: int = RICH_INDENT_SIZE,
|
210
207
|
max_length: int | None = RICH_MAX_LENGTH,
|
@@ -222,6 +219,7 @@ def make_except_hook(
|
|
222
219
|
start=start,
|
223
220
|
version=version,
|
224
221
|
path=path,
|
222
|
+
path_max_age=path_max_age,
|
225
223
|
max_width=max_width,
|
226
224
|
indent_size=indent_size,
|
227
225
|
max_length=max_length,
|
@@ -242,6 +240,7 @@ def _make_except_hook_inner(
|
|
242
240
|
start: MaybeCallableZonedDateTimeLike = get_now,
|
243
241
|
version: MaybeCallableVersionLike | None = None,
|
244
242
|
path: MaybeCallablePathLike | None = None,
|
243
|
+
path_max_age: Delta | None = None,
|
245
244
|
max_width: int = RICH_MAX_WIDTH,
|
246
245
|
indent_size: int = RICH_INDENT_SIZE,
|
247
246
|
max_length: int | None = RICH_MAX_LENGTH,
|
@@ -258,11 +257,8 @@ def _make_except_hook_inner(
|
|
258
257
|
slim = format_exception_stack(exc_val, header=True, start=start, version=version)
|
259
258
|
_ = sys.stderr.write(f"{slim}\n") # don't 'from sys import stderr'
|
260
259
|
if path is not None:
|
261
|
-
path = (
|
262
|
-
|
263
|
-
.joinpath(format_compact(to_local_plain(get_now())))
|
264
|
-
.with_suffix(".txt")
|
265
|
-
)
|
260
|
+
path = to_path(path)
|
261
|
+
path_log = path.joinpath(to_local_plain(get_now())).with_suffix(".txt")
|
266
262
|
full = format_exception_stack(
|
267
263
|
exc_val,
|
268
264
|
header=True,
|
@@ -276,8 +272,10 @@ def _make_except_hook_inner(
|
|
276
272
|
max_depth=max_depth,
|
277
273
|
expand_all=expand_all,
|
278
274
|
)
|
279
|
-
with writer(
|
275
|
+
with writer(path_log, overwrite=True) as temp:
|
280
276
|
_ = temp.write_text(full)
|
277
|
+
if path_max_age is not None:
|
278
|
+
_make_except_hook_purge(path, path_max_age)
|
281
279
|
if slack_url is not None: # pragma: no cover
|
282
280
|
from utilities.slack_sdk import SendToSlackError, send_to_slack
|
283
281
|
|
@@ -285,13 +283,23 @@ def _make_except_hook_inner(
|
|
285
283
|
send_to_slack(slack_url, f"```{slim}```")
|
286
284
|
except SendToSlackError as error:
|
287
285
|
_ = stderr.write(f"{error}\n")
|
288
|
-
|
289
286
|
if to_bool(pudb): # pragma: no cover
|
290
287
|
from pudb import post_mortem
|
291
288
|
|
292
289
|
post_mortem(tb=traceback, e_type=exc_type, e_value=exc_val)
|
293
290
|
|
294
291
|
|
292
|
+
def _make_except_hook_purge(path: PathLike, max_age: Delta, /) -> None:
|
293
|
+
threshold = get_now() - max_age
|
294
|
+
paths = {
|
295
|
+
p
|
296
|
+
for p in Path(path).iterdir()
|
297
|
+
if p.is_file() and (parse_plain_local(p.stem) <= threshold)
|
298
|
+
}
|
299
|
+
for p in paths:
|
300
|
+
p.unlink(missing_ok=True)
|
301
|
+
|
302
|
+
|
295
303
|
@dataclass(kw_only=True, slots=True)
|
296
304
|
class MakeExceptHookError(Exception):
|
297
305
|
@override
|