Catch exceptions, log them, and optionally report to an agent — without crashing your app.
Project description
oopsys-python
oops, system crashed :)
Table of contents
About
API reference
- API reference — overview
- configure()
- guard
- fallback — what to return on error
- timeit
- ignore
- install()
- OOPSYS_LOGGER_NAME
- Severity
- ErrorReport
Practice
Other
About the library
oopsys-python catches exceptions in your Python code instead of
crashing the whole process.
What happens on error:
- The exception is caught (if the function is under
@guardorinstall()is active). - An error or critical entry is written to structlog (channel
OOPSYS). - Optionally a JSON report is sent to an HTTP agent.
- Caller code receives
fallback(e.g.None) — noraiseand no traceback in the application console.
What oopsys does not do:
- does not read project env without the
OOPSYS_prefix, does not manage app configuration; - does not configure
structlog; - does not catch
KeyboardInterrupt/SystemExit(Ctrl+C works as usual).
Sync and async are detected automatically.
What the library does
| Capability | How to enable | Why |
|---|---|---|
| Don't crash on function error | @guard |
Worker, HTTP, parser — loop keeps running |
| Return a value instead of raising | @guard(fallback=...) |
if result is None without try/except |
| Measure call duration (dev) | @timeit |
DEBUG log ms (milliseconds) and hf (human-friendly) |
| Skip a function | @ignore |
Error propagates as without oopsys |
| Catch "forgotten" exceptions | install() |
Critical in log/agent (optional) |
| Report to agent | OOPSYS_AGENT__ENABLED=true |
Monitoring outside the app |
| Log only, no agent | OOPSYS_AGENT__ENABLED=false |
Local debugging |
| Dev: log then raise again | OOPSYS_RERAISE=true |
See traceback while developing |
Installation
Repository: https://github.com/morington/oopsys-python
PyPI
uv add oopsys-python
GitHub (main branch)
uv add "oopsys-python @ git+https://github.com/morington/oopsys-python.git"
GitHub (specific tag)
uv add "oopsys-python @ git+https://github.com/morington/oopsys-python.git@v0.1.0"
Quick start
1. Copy .env.example → .env.
2. In your structlog configuration add the OOPSYS channel (see OOPSYS_LOGGER_NAME). Easiest with kitstructlog.
3. In main:
from oopsys_python import configure, guard
from myapp.configuration import Configuration, Loggers
@guard(fallback=None)
async def worker() -> str:
return await do_work()
async def main() -> None:
app = Configuration() # plain pydantic-settings configuration
Loggers(developer_mode=app.is_development) # kitstructlog
configure()
result = await worker()
if result is None:
return # error already logged
API reference
Briefly: what to call, what to put on functions, what to import only for reference.
configure()
Purpose: enable oopsys once per process.
Usage:
configure()
- Reads
.env/ variables with theOOPSYS_prefix (list). - Call after
Loggers(...), before guarded functions. - Does not touch logging setup.
There is no settings class in the public import.
guard
Purpose: main decorator — an error inside the function does not crash the program.
Usage:
@guard
@guard(fallback=None)
@guard(critical=True, reraise=False)
async def fetch() -> dict | None:
...
| Outcome | Result |
|---|---|
| Success | Normal return |
| Error | Log + optional agent + return fallback |
reraise=True |
Log and raise again |
| Ctrl+C | Not handled by oopsys |
Where to apply: main, loop worker, create_task, scheduler job — not on
every small helper.
Parameters: fallback, critical, reraise — see fallback.
fallback
Purpose: what the function returns if an exception occurred inside.
Default is fallback=None.
@guard(fallback=None)
async def load() -> dict | None:
return await api.get_json()
data = await load()
if data is None:
... # failure already in OOPSYS log
fallback |
When it fits |
|---|---|
None |
"No data" → if x is None |
False |
Function returns bool |
[], {} |
Empty result (prefer None + create a new list yourself) |
{"ok": False} |
Single "error" API response shape |
This is not a callback: fallback=lambda: [] will not be called.
Same fallback for any error; check the log for type (error_type,
detail).
timeit
Purpose: in development, log call duration.
@timeit
@timeit(label="fetch")
def work() -> None:
...
- Level DEBUG, channel
OOPSYS. - Fields:
ms=69.15,hf=0:00:00.0692. - Only when
OOPSYS_IS_DEVELOPMENT=true; no-op in prod.
With guard:
@guard(fallback=None)
@timeit
async def fetch_fact() -> str:
...
ignore
Purpose: exclude a function from oopsys handling.
@ignore
def strict_check() -> None:
raise ValueError("must propagate")
Such exceptions are not swallowed by @guard or caught by install().
install()
Purpose: safety net for unhandled exceptions.
configure()
install()
Catches "leaks" in the main thread, threads, asyncio → critical log + agent.
Does not replace @guard for long-running loops.
OOPSYS_LOGGER_NAME
(Example for kitstructlog)
Purpose: constant "OOPSYS" — logger name for LoggerReg.
from kitstructlog import InitLoggers, LoggerReg
from oopsys_python import OOPSYS_LOGGER_NAME
class Loggers(InitLoggers):
main = LoggerReg(name="MAIN", level=LoggerReg.Level.INFO)
oopsys = LoggerReg(name=OOPSYS_LOGGER_NAME, level=LoggerReg.Level.INFO)
Different name: OOPSYS_LOGGER_NAME=... in .env and the same name in LoggerReg.
Levels (DEBUG, ERROR, …) are set in Loggers.
Severity
Purpose: severity level in the agent report.
| Value | When |
|---|---|
Severity.ERROR |
Error in @guard, process still alive |
Severity.CRITICAL |
install() or @guard(critical=True) |
You usually don't set this in code — oopsys assigns it.
ErrorReport
Purpose: JSON report model for the agent and tests.
Fields: severity, service, environment, exception_type, message,
traceback, timestamp, context.
{
"severity": "error",
"service": "my-app",
"environment": "production",
"exception_type": "ValueError",
"message": "invalid literal for int()",
"traceback": "...",
"timestamp": "2026-06-01T12:00:00Z",
"context": {"callable": "compute"}
}
You don't need to construct it manually.
Settings (env)
Only the OOPSYS_ prefix. Template: .env.example.
OOPSYS_IS_DEVELOPMENT=false
OOPSYS_SERVICE_NAME=app
OOPSYS_LOGGER_NAME=OOPSYS
OOPSYS_RERAISE=false
OOPSYS_AGENT__ENABLED=false
OOPSYS_AGENT__HOST=localhost
OOPSYS_AGENT__PORT=8080
OOPSYS_AGENT__PATH=/reports
OOPSYS_AGENT__TIMEOUT=3.0
| Variable | Effect |
|---|---|
OOPSYS_IS_DEVELOPMENT |
timeit, environment in report |
OOPSYS_SERVICE_NAME |
Name in JSON |
OOPSYS_LOGGER_NAME |
structlog channel |
OOPSYS_RERAISE |
raise again after log |
OOPSYS_AGENT__ENABLED |
false — log only |
OOPSYS_AGENT__* |
Agent URL and timeout |
Agent delivery failure does not crash the application.
Startup order
.envwithOOPSYS_*(from.env.example).- In
Loggers— channel OOPSYS_LOGGER_NAME. Loggers(...)→ configure() → optionally install().- On workers — guard (+ timeit in dev).
- After call — check fallback (
if result is None).
Usage scenarios
Long-running worker (parser, polling)
Network fails — iteration returns None, loop does not stop.
@guard(fallback=None)
@timeit
async def fetch_fact() -> str:
...
async def main() -> None:
configure()
while True:
fact = await fetch_fact()
if fact is None:
await asyncio.sleep(5)
continue
logger.info("fact", fact=fact)
await asyncio.sleep(15)
Example: examples/parser.py.
Sync script with bad input
Each compute call is isolated — one invalid line does not stop the whole run.
@guard(fallback=None)
@timeit
def compute(expr: str) -> int | None:
...
for expr in expressions:
if (r := compute(expr)) is not None:
logger.info("result", result=r)
Example: examples/protected_sync.py.
Async + install + ignore
configure()
install()
@guard(fallback=None)
async def divide(a, b): ...
@ignore
async def must_fail(): ...
Example: examples/protected_async.py.
Scheduler (APScheduler and similar)
@guard(fallback=None)
async def scheduled_job() -> None:
...
scheduler.add_job(scheduled_job, "interval", seconds=30)
One job failure — next tick runs on schedule.
CLI: single run under guard
@guard(critical=True)
def main() -> None:
...
if __name__ == "__main__":
configure()
main()
Log only, no agent
In .env: OOPSYS_AGENT__ENABLED=false. Errors in OOPSYS channel, no HTTP.
Dev: see traceback after log
OOPSYS_RERAISE=true or @guard(reraise=True).
Examples in the repository
cp .env.example .env
uv run python examples/protected_sync.py
uv run python examples/protected_async.py
uv run python examples/parser.py
| File | Demonstrates |
|---|---|
protected_sync.py |
guard, timeit, sync |
protected_async.py |
guard, ignore, install |
parser.py |
loop, HTTP, fallback=None |
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file oopsys_python-0.1.1.tar.gz.
File metadata
- Download URL: oopsys_python-0.1.1.tar.gz
- Upload date:
- Size: 11.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.17 {"installer":{"name":"uv","version":"0.11.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"13","id":"trixie","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0e2645ef28a114bac0ba442adc4ec6ca575228877cae0fa32882a9927fd9d97e
|
|
| MD5 |
0836420af38bb7b4713b785669c97b86
|
|
| BLAKE2b-256 |
c330be69866ddb3f8612c317e77f62d7c34ad71f1ca2175364e504f16acdd535
|
File details
Details for the file oopsys_python-0.1.1-py3-none-any.whl.
File metadata
- Download URL: oopsys_python-0.1.1-py3-none-any.whl
- Upload date:
- Size: 10.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.17 {"installer":{"name":"uv","version":"0.11.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"13","id":"trixie","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fb84847a038631c97ca8887a4b6cce2081135596744f57ed9855d0f060ad518d
|
|
| MD5 |
c35b5b4c3d60700ddfdadc75e69bdfe3
|
|
| BLAKE2b-256 |
06bc41986b42e87679c6660b3095083b420e0154769dc4f420d9a972544b42af
|