SimPyTick
Simple Python Tick: periodic events using a spawn-isolated process or a thread.
Requires Python 3.10 or later. Runtime dependencies: psutil and coloredlogs.
Release Status
Releases before 1.0.0 are testing versions and may introduce breaking changes
without a major version increment. Semantic versioning will begin with 1.0.0.
Installation
pip install SimPyTick
From a checkout of this package:
uv pip install .
Usage
from simpytick.timer import TimedEvent
def main():
with TimedEvent(s=0.01, n=100, backend="thread") as ticks:
for tick in ticks:
print(tick)
if __name__ == "__main__":
main()
TimedEvent(s, n, compensation=True, logger=None, backend="process") uses a
finite, positive period s and an integer count n. Booleans are not accepted
as periods or counts. n=-1 runs indefinitely; n=0 yields no ticks. Invalid
arguments raise TypeError or ValueError before any worker is created. Exiting the
context terminates and joins the process, or signals and joins the thread.
The first tick is emitted immediately; subsequent ticks use the given interval.
The default process backend uses multiprocessing's spawn context. Keep process
creation behind if __name__ == "__main__": in an importable script. Both backends
retain normal OS priority; the package does not request real-time scheduling or
require priority-changing privileges. Failed startup cleans up any started worker
and its status connections. Use backend="thread" to
avoid child creation after libraries start threads or open pollers.
Background failures raise RuntimeError during iteration or normal context exit,
even without a logger. Reported exceptions include their type and a bounded message;
abrupt exits without completion are also errors. Health-check waits are capped at
100 ms, subject to OS scheduling. Application exceptions remain primary during
cleanup. Normal completion with collapsed event signals still ends iteration early.
The low-level API keeps its positional arguments and validates n and s using
the same rules:
import threading
from simpytick.timer import timer
event = threading.Event()
timer(3, 0.01, event, False, None)
timer(n, s, event, enable_compensation=True, logger_level=None, *, stop_event=None) sets the supplied event. Consumers clear it after each tick;
multiple unconsumed signals can collapse into one. This low-level call blocks
until completion. The first tick fires immediately. With compensation enabled,
a persistent monotonic schedule corrects accumulated drift by shortening waits,
including zero waits to catch up after missed deadlines. With compensation
disabled, each tick is followed by a fixed interval wait. This is not a hard
real-time scheduler, and slow consumers may miss ticks. Compensation no longer
restricts the number of decimal places in the period; this does not guarantee
that the operating system can achieve very short intervals.
Sleep
import threading
import simpytick
simpytick.sleep(0.01)
stop = threading.Event()
interrupted = simpytick.sleep(1.0, stop_event=stop, check_interval=0.01)
simpytick.sleep(seconds, *, stop_event=None, check_interval=0.01) returns
True if cancelled and False when the wait completes. Another thread can
request cancellation with stop.set(). Without a stop event it uses a single
time.sleep() call; otherwise it sleeps in chunks, checking the stop flag
between them. seconds must be finite and nonnegative; check_interval must
be finite and positive.
An active sleep cannot be interrupted. Cancellation latency depends on the
chunk size and OS scheduling, so check_interval is not a guaranteed bound.
Monotonic deadlines account for chunk overhead without busy-waiting. Separate
calls do not share a schedule or compensate for work between calls; use
sleep_2 or TimedEvent for periodic drift compensation. Exact wake-up times are not
guaranteed.
Compensated Sleep
import time
from simpytick import sleep_2
sleep = sleep_2()
for _ in range(100):
sleep(0.01)
print(time.time())
Create the callable once outside the loop. It runs in the calling thread, with no background thread or process. Its monotonic schedule starts on the first call; subsequent calls advance the deadline by the supplied seconds, accounting for work and oversleep between calls. Overdue deadlines produce zero waits until caught up. Use a separate instance for each loop; instances are not thread-safe.
Calls accept the same stop_event and check_interval keyword arguments and
return the same cancellation flag as simpytick.sleep(). Cancellation resets the
schedule; sleep.reset() also makes the next call start a new schedule. Exact
wake-up times and immediate cancellation are not guaranteed.
Migration Scope
SimPyTickLogger adapts standard named loggers and supports the custom iinfo()
and idebug() methods, handler and level access, and adapter caching. Imports do
not change the global logger class or register level names; explicit
get_logger() calls register custom levels and configure output. The package
installs a NullHandler for application-controlled logging. By default, file
output goes to simpytickLogs under the system temporary directory.
Tests cover timer APIs, lifecycle and worker failures, logger import isolation, and matching runtime and distribution versions. Spawn-worker tests exercise actual processes without mocking priority setup.
Distribution name: SimPyTick. Import namespace: simpytick. Initial
standalone version: 0.0.1. Original BSD-3-Clause license and attribution remain.
Development
Install uv, then run from this repository's root. Python 3.14 matches the mandatory CI lint runtime:
uv venv --python 3.14
uv pip install -e . -r requirements-dev.txt
No environment activation is needed for uv run. The editable install makes
local source changes available without reinstalling. Run the CI unit check
(including coverage) and lint check:
uv run python .github/scripts/run_checks.py unit
uv run python .github/scripts/run_checks.py lint
Run a single test module while iterating, then build and validate distributions:
uv run python -m unittest tests.test_sleep
uv build
uvx twine check dist/*
Contributor and agent context is in AGENTS.md. Add regression tests for behavior changes; test and lint before opening a pull request. Worker changes must also pass the spawned-process tests in the full unit suite.
requirements-dev.txt includes -r requirements.txt and adds development-only
pylint==4.0.10 and coverage>=7.10,<8; these tools are not published as runtime
dependencies. CI reports coverage without a minimum. Pylint keeps its default
fail-under of 10, with no disabled messages or explicit custom threshold.
requirements.txt contains runtime-only dependencies and mirrors
pyproject.toml project dependencies. The workflows use it before tests, build,
and publication preflight; the requirements guard test detects drift, so update
both files together. PDM's declared build backend supplies uv build build
dependencies, while the generated wheel and source-distribution metadata causes
pip installs to resolve runtime dependencies automatically. The requirements
file is included in source distributions. MANIFEST.in is unnecessary; source
distributions also include tests and workflows. No protobuf generation or
parent-project build hook is used.
CI and Publication
The workflows assume this directory is the root of its own GitHub repository. While nested in the original repository, GitHub does not discover these workflows.
test.yml: mandatory Python 3.10-3.14 tests onubuntu-latest,windows-latest,macos-latest,ubuntu-22.04-arm, andubuntu-24.04-arm, plus nonblocking3.xprobes on the same five runners; coverage run/report has no minimum.pylint.yml: Python 3.14 / Ubuntu, Pylint 4.0.10,--jobs=1 simpytick tests.build.yml: additional Ubuntu / Python 3.14 preflight, wheel/sdist build, Twine checks, isolated installed-artifact smoke tests, and artifact upload.weekly-latest-python.yml: Monday at 12:00 UTC and manual runs; nonblocking3.xunit probes on the same five runners and Ubuntu lint with latest Pylint.verify-workflows.yml: reference-equivalent actionlint syntax/expression checks and zizmor security audit.pypi_publish.yml: pushes tomaster, not GitHub Release publication. A read-only build job runs preflight tests, build, Twine and wheel/sdist smoke checks, then uploads validated artifacts. Its dependentpypi-releaseenvironment job publishes them using OIDC withskip-existing: true,print-hash: false, andattestations: true, without stored API tokens.
After publication succeeds, an annotated v<version> tag is created from
simpytick/_version.py. An existing tag on the same commit is an idempotent
no-op; a tag on a different commit is never moved and causes failure. Set the
version constant manually before a new release. No automatic version bump or
semantic-version inference from commits or tags occurs; tags are output, not
the version source. PDM reads the same file for distribution metadata, and
simpytick.__version__ imports it at runtime.
Before publishing, configure the PyPI project SimPyTick with a trusted
publisher matching your GitHub owner/repository, workflow pypi_publish.yml, and
environment pypi-release. Keep the workflow filename to preserve that identity
and retain the environment URL https://pypi.org/project/SimPyTick/. Confirm PyPI
name availability and select an unpublished version before pushing. Skipping
existing files supports retries, not replacement of published artifacts.
There is no full-matrix mandatory CI needs barrier before publishing, matching
the reference. Require supported-version tests, lint, build and workflow audit
checks through master branch protection; configure environment reviewers and
allowed branches. Do not require floating 3.x probes as support guarantees.
See .github/CI.md for CI ownership, reference differences,
and justified deviations.
Todos
AI Findings
Remaining Medium/Low findings from the pre-publication review. High-severity findings were addressed separately; these items remain open.
- Medium - Tick delivery and overrun accounting (review #7): Define whether
delivery coalesces, skips, or guarantees ticks. Reconcile the exactly-
nexample with missed signals, implement accurateoverrun_countand sequence accounting, and test consumers that span several periods. Avoid an unbounded queue. - Medium - Context lifecycle (review #8): Reject repeated/nested entry or implement a fully reset lifecycle. Define iteration outside an active context; test re-entry, nested entry, and reuse after thread shutdown.
- Medium - Scheduling scalability (review #9): Define catch-up/skip policy and bound catch-up bursts. Benchmark CPU use, jitter, startup, and shutdown over increasing timer counts; assess chunked cancellation wakeups and worker-per-timer overhead before making high-precision or scalability claims.
- Medium - Log-file path safety (review #10): Separate logger identity from filenames, validate filenames, and use an application-owned directory. Make file output opt-in and address path traversal and shared-temporary-directory symlinks.
- Medium - Log retention and writers (review #11): Use a stable log basename
so retention works across daily restarts. Give rotation a single owner for
multiprocess use, such as a
QueueListener; test retention and concurrent writers. - Medium - Release gates (review #12): Make publication depend on the full supported OS/Python test matrix and validated distributions for the release commit. Check the release tag against the distribution version.
- Medium - Dependency and supply-chain validation (review #13): Commit a
development lockfile, lock release tooling, test minimum and current runtime
dependencies, add scheduled advisory audits, and pin the publishing action to a
verified commit SHA. Reassess the now-unused
psutildependency after removing automatic priority changes. - Low - Packaged development output (review #14): Exclude or remove
simpytick/results.txtfrom distributions. Keep reproducible benchmark methods and meaningful results outside the import package, and inspect wheel/sdist contents.
Metadata
Release files for SimPyTick 0.0.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| simpytick-0.0.2.tar.gz | 39.2 kB | With provenance | Details for simpytick-0.0.2.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| simpytick-0.0.2-py3-none-any.whl | Python 3 | none | any | Details for simpytick-0.0.2-py3-none-any.whl |
Total release size: 56.3 kB
Release files / simpytick-0.0.2.tar.gz
| Download URL | simpytick-0.0.2.tar.gz |
|---|---|
| Size | 39.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
cdff96fdfb6f3a060671a3f61e68a51046dfbf8c3d14595741488c63f8c51c7b
|
|
BLAKE2b-256 checksum How to use checksums |
a5ca4aec6c900c8892f359f782e84cc0417f6d8fdf914e04791c0fd002cff27f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 1, 2026.
Transparency logRelease files / simpytick-0.0.2-py3-none-any.whl
| Download URL | simpytick-0.0.2-py3-none-any.whl |
|---|---|
| Size | 17.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
bd992958cd7bd00635f15ac46556da560107e542a2b1cec55e7390c29d826a77
|
|
BLAKE2b-256 checksum How to use checksums |
d6806c99a60d76eb4d986c612e1a85e09ed4af58514b787ebe2c50710a06f0d5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 1, 2026.
Transparency logSecurity for SimPyTick 0.0.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for simpytick-0.0.2.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 1, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.