aethellayer 0.1.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.
@@ -0,0 +1,46 @@
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ # dependencies
4
+ /node_modules
5
+ /.pnp
6
+ .pnp.*
7
+ .yarn/*
8
+ !.yarn/patches
9
+ !.yarn/plugins
10
+ !.yarn/releases
11
+ !.yarn/versions
12
+
13
+ # testing
14
+ /coverage
15
+
16
+ # next.js
17
+ /.next/
18
+ /out/
19
+
20
+ # production
21
+ /build
22
+
23
+ # misc
24
+ .DS_Store
25
+ *.pem
26
+ .venv/
27
+ packages/python/dist/
28
+ packages/python/*.egg-info/
29
+ packages/sdk/dist/
30
+
31
+ # debug
32
+ npm-debug.log*
33
+ yarn-debug.log*
34
+ yarn-error.log*
35
+ .pnpm-debug.log*
36
+
37
+ # env files (can opt-in for committing if needed)
38
+ .env*
39
+ !.env.example
40
+
41
+ # vercel
42
+ .vercel
43
+
44
+ # typescript
45
+ *.tsbuildinfo
46
+ next-env.d.ts
@@ -0,0 +1,85 @@
1
+ Metadata-Version: 2.5
2
+ Name: aethellayer
3
+ Version: 0.1.0
4
+ Summary: AethelLayer probe for FastAPI, Starlette, and Python CLIs. Catch silent onboarding failures and open an inbox thread.
5
+ Project-URL: Homepage, https://aethellayer.com
6
+ Project-URL: Documentation, https://aethellayer.com
7
+ Project-URL: Repository, https://github.com/aethellayer/aeth
8
+ Author: AethelLayer
9
+ License-Expression: MIT
10
+ Keywords: aethellayer,developer-experience,fastapi,observability,sdk
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Software Development :: Libraries
20
+ Requires-Python: >=3.10
21
+ Provides-Extra: dev
22
+ Requires-Dist: build; extra == 'dev'
23
+ Requires-Dist: twine; extra == 'dev'
24
+ Provides-Extra: fastapi
25
+ Requires-Dist: fastapi>=0.110; extra == 'fastapi'
26
+ Requires-Dist: starlette>=0.36; extra == 'fastapi'
27
+ Description-Content-Type: text/markdown
28
+
29
+ # aethellayer (Python)
30
+
31
+ Probe for AethelLayer. Catch silent FastAPI, Starlette, Flask-style, and CLI failures.
32
+
33
+ ## Install
34
+
35
+ ```bash
36
+ pip install aethellayer
37
+ export AETHEL_API_KEY=aethel_live_...
38
+ ```
39
+
40
+ ## FastAPI
41
+
42
+ ```python
43
+ from fastapi import FastAPI
44
+ from aethellayer import Probe
45
+
46
+ app = FastAPI()
47
+ probe = Probe.from_env(environment="api_middleware", app_name="payments-api")
48
+ app.add_middleware(probe.fastapi_middleware())
49
+ ```
50
+
51
+ ## Capture manually
52
+
53
+ ```python
54
+ probe.capture(
55
+ error_code="RATE_LIMIT_429",
56
+ endpoint_path="GET /v1/keys",
57
+ taxonomy="RateLimit",
58
+ message="Starter quota burned before noon",
59
+ )
60
+ ```
61
+
62
+ ## CLI
63
+
64
+ ```bash
65
+ aethellayer init
66
+ aethellayer run -- python your_cli.py
67
+ ```
68
+
69
+ Or in code:
70
+
71
+ ```python
72
+ from aethellayer import Probe
73
+
74
+ with Probe.from_env(environment="cli").wrap_cli():
75
+ main()
76
+ ```
77
+
78
+ ## Publish (maintainers)
79
+
80
+ ```bash
81
+ cd packages/python
82
+ python -m pip install build twine
83
+ python -m build
84
+ python -m twine upload dist/*
85
+ ```
@@ -0,0 +1,57 @@
1
+ # aethellayer (Python)
2
+
3
+ Probe for AethelLayer. Catch silent FastAPI, Starlette, Flask-style, and CLI failures.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install aethellayer
9
+ export AETHEL_API_KEY=aethel_live_...
10
+ ```
11
+
12
+ ## FastAPI
13
+
14
+ ```python
15
+ from fastapi import FastAPI
16
+ from aethellayer import Probe
17
+
18
+ app = FastAPI()
19
+ probe = Probe.from_env(environment="api_middleware", app_name="payments-api")
20
+ app.add_middleware(probe.fastapi_middleware())
21
+ ```
22
+
23
+ ## Capture manually
24
+
25
+ ```python
26
+ probe.capture(
27
+ error_code="RATE_LIMIT_429",
28
+ endpoint_path="GET /v1/keys",
29
+ taxonomy="RateLimit",
30
+ message="Starter quota burned before noon",
31
+ )
32
+ ```
33
+
34
+ ## CLI
35
+
36
+ ```bash
37
+ aethellayer init
38
+ aethellayer run -- python your_cli.py
39
+ ```
40
+
41
+ Or in code:
42
+
43
+ ```python
44
+ from aethellayer import Probe
45
+
46
+ with Probe.from_env(environment="cli").wrap_cli():
47
+ main()
48
+ ```
49
+
50
+ ## Publish (maintainers)
51
+
52
+ ```bash
53
+ cd packages/python
54
+ python -m pip install build twine
55
+ python -m build
56
+ python -m twine upload dist/*
57
+ ```
@@ -0,0 +1,157 @@
1
+ """AethelLayer Python probe."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ import sys
8
+ import traceback
9
+ import urllib.error
10
+ import urllib.request
11
+ from dataclasses import dataclass, field
12
+ from typing import Any, Callable, Mapping, MutableMapping, Optional
13
+
14
+ DEFAULT_ENDPOINT = "https://api.aethellayer.com/v1/probes"
15
+
16
+
17
+ @dataclass
18
+ class Probe:
19
+ api_key: str
20
+ environment: str = "api_middleware"
21
+ endpoint: str = DEFAULT_ENDPOINT
22
+ app_name: Optional[str] = None
23
+ metadata: Mapping[str, Any] = field(default_factory=dict)
24
+
25
+ def __post_init__(self) -> None:
26
+ if not self.api_key:
27
+ raise ValueError("Probe requires api_key (AETHEL_API_KEY)")
28
+
29
+ @classmethod
30
+ def from_env(cls, **kwargs: Any) -> "Probe":
31
+ key = kwargs.pop("api_key", None) or os.environ.get("AETHEL_API_KEY")
32
+ if not key:
33
+ raise ValueError("Set AETHEL_API_KEY or pass api_key=")
34
+ return cls(api_key=key, **kwargs)
35
+
36
+ def capture(
37
+ self,
38
+ *,
39
+ error_code: str,
40
+ message: Optional[str] = None,
41
+ stack: Optional[str] = None,
42
+ endpoint_path: Optional[str] = None,
43
+ taxonomy: Optional[str] = None,
44
+ metadata: Optional[Mapping[str, Any]] = None,
45
+ ) -> dict[str, Any]:
46
+ body = {
47
+ "error_code": error_code,
48
+ "stack_trace": stack or message,
49
+ "endpoint_path": endpoint_path,
50
+ "taxonomy_category": taxonomy,
51
+ "environment": self.environment,
52
+ "app_name": self.app_name,
53
+ "metadata": {
54
+ "runtime": f"python {sys.version.split()[0]}",
55
+ **dict(self.metadata),
56
+ **dict(metadata or {}),
57
+ },
58
+ }
59
+ data = json.dumps(body).encode("utf-8")
60
+ req = urllib.request.Request(
61
+ self.endpoint,
62
+ data=data,
63
+ method="POST",
64
+ headers={
65
+ "content-type": "application/json",
66
+ "authorization": f"Bearer {self.api_key}",
67
+ },
68
+ )
69
+ try:
70
+ with urllib.request.urlopen(req, timeout=5) as res:
71
+ return {"ok": True, "status": getattr(res, "status", 200)}
72
+ except urllib.error.HTTPError as err:
73
+ return {"ok": False, "status": err.code}
74
+ except Exception as err: # noqa: BLE001 - probe must never crash the host
75
+ return {"ok": False, "status": 0, "error": str(err)}
76
+
77
+ def capture_exception(
78
+ self,
79
+ exc: BaseException,
80
+ *,
81
+ error_code: str = "UNCAUGHT",
82
+ endpoint_path: Optional[str] = None,
83
+ taxonomy: str = "Uncaught",
84
+ ) -> dict[str, Any]:
85
+ return self.capture(
86
+ error_code=error_code,
87
+ message=str(exc),
88
+ stack="".join(traceback.format_exception(type(exc), exc, exc.__traceback__)),
89
+ endpoint_path=endpoint_path,
90
+ taxonomy=taxonomy,
91
+ )
92
+
93
+ def fastapi_middleware(self):
94
+ """Starlette/FastAPI BaseHTTPMiddleware that captures 5xx responses."""
95
+ from starlette.middleware.base import BaseHTTPMiddleware
96
+ from starlette.requests import Request
97
+ from starlette.responses import Response
98
+
99
+ probe = self
100
+
101
+ class AethelMiddleware(BaseHTTPMiddleware):
102
+ async def dispatch(self, request: Request, call_next: Callable) -> Response:
103
+ try:
104
+ response = await call_next(request)
105
+ except Exception as exc: # noqa: BLE001
106
+ probe.capture_exception(
107
+ exc,
108
+ error_code="UNCAUGHT_MIDDLEWARE",
109
+ endpoint_path=f"{request.method} {request.url.path}",
110
+ )
111
+ raise
112
+ if response.status_code >= 500:
113
+ probe.capture(
114
+ error_code=f"HTTP_{response.status_code}",
115
+ endpoint_path=f"{request.method} {request.url.path}",
116
+ taxonomy="Http5xx",
117
+ )
118
+ return response
119
+
120
+ return AethelMiddleware
121
+
122
+ def wrap_cli(self, argv: Optional[list[str]] = None):
123
+ """Context manager / decorator helper for CLI entrypoints."""
124
+ argv = argv or sys.argv
125
+ probe = self
126
+
127
+ class _Session:
128
+ def __enter__(self):
129
+ return self
130
+
131
+ def __exit__(self, exc_type, exc, tb):
132
+ if exc is not None:
133
+ probe.capture_exception(
134
+ exc,
135
+ error_code="CLI_FAILURE",
136
+ endpoint_path=" ".join(argv),
137
+ taxonomy="Cli",
138
+ )
139
+ return False
140
+
141
+ def run(self, fn: Callable[[], Any]) -> Any:
142
+ try:
143
+ return fn()
144
+ except Exception as exc: # noqa: BLE001
145
+ probe.capture_exception(
146
+ exc,
147
+ error_code="CLI_FAILURE",
148
+ endpoint_path=" ".join(argv),
149
+ taxonomy="Cli",
150
+ )
151
+ raise
152
+
153
+ return _Session()
154
+
155
+
156
+ def probe(**kwargs: Any) -> Probe:
157
+ return Probe.from_env(**kwargs) if "api_key" not in kwargs else Probe(**kwargs)
@@ -0,0 +1,62 @@
1
+ """CLI for the aethellayer Python package."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import os
7
+ import subprocess
8
+ import sys
9
+
10
+ from . import Probe
11
+
12
+
13
+ def main(argv: list[str] | None = None) -> None:
14
+ parser = argparse.ArgumentParser(prog="aethellayer", description="AethelLayer Python probe")
15
+ sub = parser.add_subparsers(dest="cmd")
16
+
17
+ sub.add_parser("init", help="Print install steps")
18
+ run = sub.add_parser("run", help="Wrap a command and capture non-zero exits")
19
+ run.add_argument("command", nargs=argparse.REMAINDER, help="Command after --")
20
+
21
+ args = parser.parse_args(argv)
22
+ if args.cmd == "init" or args.cmd is None:
23
+ print(
24
+ """AethelLayer Python init
25
+
26
+ 1. pip install aethellayer
27
+ 2. export AETHEL_API_KEY=aethel_live_...
28
+ 3. FastAPI:
29
+ from aethellayer import Probe
30
+ app.add_middleware(Probe.from_env(app_name="api").fastapi_middleware())
31
+ 4. CLI:
32
+ aethellayer run -- python your_cli.py
33
+
34
+ Open the dashboard inbox after the first failure.
35
+ """
36
+ )
37
+ return
38
+
39
+ if args.cmd == "run":
40
+ command = [c for c in args.command if c != "--"]
41
+ if not command:
42
+ print("Usage: aethellayer run -- <command> [...args]", file=sys.stderr)
43
+ sys.exit(1)
44
+ api_key = os.environ.get("AETHEL_API_KEY")
45
+ if not api_key:
46
+ print("Missing AETHEL_API_KEY", file=sys.stderr)
47
+ sys.exit(1)
48
+ probe = Probe(api_key=api_key, environment="cli")
49
+ result = subprocess.run(command)
50
+ if result.returncode != 0:
51
+ probe.capture(
52
+ error_code=f"CLI_EXIT_{result.returncode}",
53
+ endpoint_path=" ".join(command),
54
+ taxonomy="Cli",
55
+ )
56
+ sys.exit(result.returncode)
57
+
58
+ parser.print_help()
59
+
60
+
61
+ if __name__ == "__main__":
62
+ main()
@@ -0,0 +1,40 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "aethellayer"
7
+ version = "0.1.0"
8
+ description = "AethelLayer probe for FastAPI, Starlette, and Python CLIs. Catch silent onboarding failures and open an inbox thread."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ authors = [{ name = "AethelLayer" }]
13
+ keywords = ["aethellayer", "sdk", "observability", "fastapi", "developer-experience"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Topic :: Software Development :: Libraries",
24
+ ]
25
+ dependencies = []
26
+
27
+ [project.optional-dependencies]
28
+ fastapi = ["fastapi>=0.110", "starlette>=0.36"]
29
+ dev = ["build", "twine"]
30
+
31
+ [project.urls]
32
+ Homepage = "https://aethellayer.com"
33
+ Documentation = "https://aethellayer.com"
34
+ Repository = "https://github.com/aethellayer/aeth"
35
+
36
+ [project.scripts]
37
+ aethellayer = "aethellayer.cli:main"
38
+
39
+ [tool.hatch.build.targets.wheel]
40
+ packages = ["aethellayer"]