Skip to main content

Typed, dependency-free utilities for the everyday tasks you keep re-implementing in every project.

Project description

hv-utils

Typed, dependency-free utilities for the everyday tasks you keep re-implementing in every project.

Includes: • Environment & string parsing • Cron expression evaluation • Expiration & time-based policies • Sentinel primitives (MISSING)

Requirements

  • Use: Python 3.12+; install from PyPI with pip/uv/Poetry (see install commands below); no external runtime dependencies.

Install

  • pip: pip install hv-utils
  • uv: uv add hv-utils
  • Poetry: poetry add hv-utils

Quick start

  • Install: pip install hv-utils (or add an extra such as pip install hv-utils[cron]).
  • Use:
from dataclasses import dataclass
from datetime import UTC, datetime, timedelta

from hv_utils.cron import cron_matches, parse_cron
from hv_utils.parse_env import env_bool, env_int, env_list
from hv_utils.parse_str import parse_bytes_size
from hv_utils.expiration import ExpiresAfter
from hv_utils.sentinel import MISSING

schedule = parse_cron("*/15 0-12/6 1,15 1-3 MON-FRI")
print(schedule.minute)  # (0, 15, 30, 45)
print(cron_matches(schedule, datetime(2025, 1, 13, 12, 0, tzinfo=UTC)))

# Parse environment variables with rich casting and defaults
debug = env_bool("DEBUG", default=False)
workers = env_int("WORKERS", default=4)
hosts = env_list("HOSTS", env={"HOSTS": "a.example.com,b.example.com"})
max_upload_bytes = parse_bytes_size("10MB")

expires = ExpiresAfter(ttl=timedelta(hours=1), since=datetime.now(tz=UTC))
print(expires.as_ttl())  # Remaining time before expiration, clamped to zero


@dataclass
class Payload:
    data: object = MISSING


payload = Payload()
if payload.data is MISSING:
    print("Data not provided yet")

Cron utility

  • Parse: hv_utils.cron.parse_cron(expression: str) -> CronSchedule — expands a 5-field cron string into concrete minute, hour, day-of-month, month, and day-of-week tuples. Supports literals, ranges, steps (*/n), comma lists, and case-insensitive month/day names. Day-of-week treats both 0 and 7 as Sunday; invalid input raises ValueError with a consistent message.
  • Match: hv_utils.cron.cron_matches(expression: str | CronSchedule, when: datetime) -> bool — checks whether a datetime satisfies a schedule. Day-of-month and day-of-week use cron OR semantics: if both are restricted, a match occurs when either field matches (all other fields must also match). If one is a wildcard, only the other is considered.
  • Schedule helpers:
    • CronSchedule.from_exp(expr: str) — convenience constructor around parse_cron.
    • CronSchedule.matches(dt: datetime) -> bool — instance wrapper over cron_matches.
    • CronSchedule.next(start: datetime, *, inclusive: bool = False, max_lookahead_days: int = 366) -> datetime — returns the next occurrence after start, optionally including start, bounded by max_lookahead_days.
    CronSchedule.iter(start: datetime, *, inclusive: bool = False, max_lookahead_days: int = 366) -> Iterable[datetime] — yields successive matching datetimes; callers should consume responsibly to avoid unbounded iteration.

Expiration utilities

  • Interfaces: Expiration standardizes conversion to timestamp (as_timestamp()), datetime (as_datetime(tz=UTC)), and remaining TTL (as_ttl()).
  • Relative TTL: ExpiresIn computes expiration relative to the call time; TTL is constant because the target is always "now + ttl".
  • Anchored TTL: ExpiresAfter adds a TTL to a timezone-aware start datetime and clamps negative TTL to zero.
  • Absolute: ExpiresAtTS targets a Unix timestamp; ExpiresAtDT targets a timezone-aware datetime. Both clamp expired TTLs to zero.

Sentinel utility

  • MISSING — singleton sentinel typed as Any, falsy, and raises AttributeError on attribute access. Useful for distinguishing omitted values from explicit None in dataclasses and function defaults.

Environment parsing

  • Parsing is split into two related layers:
    • String parsers (hv_utils.parse_str): reusable string-to-value helpers such as parse_bool, parse_int, parse_float, parse_decimal, parse_enum, parse_path, parse_url, parse_timedelta, parse_datetime, parse_bytes_size, parse_list, parse_set, parse_mapping, parse_json, parse_json_typed, parse_base64_bytes, parse_base64_str.
    • Env wrappers (hv_utils.parse_env): environment-aware helpers that wrap the string parsers: env_str, env_bool, env_int, env_float, env_decimal, env_enum, env_path, env_url, env_timedelta, env_datetime, env_list, env_set, env_mapping, env_json, env_base64_bytes, env_base64_str.
  • Common knobs: default supplies a fallback for missing/invalid values, while required=True raises ValueError instead of returning a default. All wrappers accept an optional env mapping for testing.

Development requirements

  • Python 3.12+ for development; optionally install 3.13/3.14 to run the full test matrix.
  • uv for dependency management; sync dev tools via uv sync --group dev.
  • Pre-commit hooks available; install with pre-commit install.

How we work

  • Functional-first utilities; keep state to a minimum.
  • Standard library only at runtime. If an optional dependency is unavoidable, add an extra in pyproject.toml, guard the import with try/except ImportError, and raise a friendly install hint.
  • Absolute imports only. Expose public helpers from src/hv_utils/__init__.py.
  • Always type everything and keep mypy --strict green.
  • TDD over heroics: write or update tests before implementing behavior, cover edge cases, and keep functions small and composable.
  • Follow ruff/PEP 8 style (line length 120).

Common commands

  • Format: uv run ruff format .
  • Lint (autofix): uv run ruff check --fix .
  • Type-check: uv run mypy src tests
  • Tests (current Python): uv run pytest
  • Tests on a specific interpreter: uv run --python 3.13 pytest
  • Full matrix (requires those interpreters installed):
    • uv run --python 3.12 pytest
    • uv run --python 3.13 pytest
    • uv run --python 3.14 pytest

Pre-commit

  • Install hooks: pre-commit install (uses your machine-level pre-commit)
  • Run all hooks manually: pre-commit run --all-files
  • Hooks auto-add the BSD-3 header (via python -m tools.copyright_header), then run the same uv-powered format, lint, type-check, and test commands listed above.

Contributing

  • Keep PRs small and focused on one utility or fix.
  • Add tests, docstrings, and __init__ exports alongside new utilities.
  • Run format, lint, mypy, and pytest before opening a PR; for compatibility-sensitive changes, run the full matrix.
  • Note: uv commands may need escalated permission in some environments. I will ask for escalation before running uv so tasks can proceed. The uv cache directory is already configured in pyproject.toml; no need to set UV_CACHE_DIR manually.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

hv_utils-0.1.0.tar.gz (17.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

hv_utils-0.1.0-py3-none-any.whl (19.6 kB view details)

Uploaded Python 3

File details

Details for the file hv_utils-0.1.0.tar.gz.

File metadata

  • Download URL: hv_utils-0.1.0.tar.gz
  • Upload date:
  • Size: 17.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.13 {"installer":{"name":"uv","version":"0.9.13"},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for hv_utils-0.1.0.tar.gz
Algorithm Hash digest
SHA256 b0c44e238a7be96e6a81bf26419273e8f488e64b2c0bd1f98f1fbe7caf559df7
MD5 02ecf856d86b4f286c25bb898e0f24ba
BLAKE2b-256 e84ac999817fbd9169df7ef2cfc3d9f4b93b0caf36f9506a7a3adc136868b22c

See more details on using hashes here.

File details

Details for the file hv_utils-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: hv_utils-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 19.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.13 {"installer":{"name":"uv","version":"0.9.13"},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for hv_utils-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ab671c2f7f862ad929b2004f194c7d5a6be58e3d75fe2823edf2ddb08080aa04
MD5 2d5800cbea600ec016a4bac0e5f72157
BLAKE2b-256 8ff655f375c25eebc791d10a6aae7306c4959ee42aa7a75f273ef2cd518e2a7e

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page