structlog-config 0.5.0__tar.gz → 0.7.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.
- {structlog_config-0.5.0 → structlog_config-0.7.0}/PKG-INFO +144 -2
- {structlog_config-0.5.0 → structlog_config-0.7.0}/README.md +140 -0
- structlog_config-0.7.0/pyproject.toml +36 -0
- {structlog_config-0.5.0 → structlog_config-0.7.0}/structlog_config/__init__.py +6 -5
- {structlog_config-0.5.0 → structlog_config-0.7.0}/structlog_config/fastapi_access_logger.py +6 -33
- {structlog_config-0.5.0 → structlog_config-0.7.0}/structlog_config/formatters.py +23 -3
- {structlog_config-0.5.0 → structlog_config-0.7.0}/structlog_config/packages.py +7 -2
- structlog_config-0.7.0/structlog_config/pytest_plugin.py +222 -0
- {structlog_config-0.5.0 → structlog_config-0.7.0}/structlog_config/stdlib_logging.py +8 -5
- {structlog_config-0.5.0 → structlog_config-0.7.0}/structlog_config/trace.py +4 -2
- structlog_config-0.5.0/pyproject.toml +0 -61
- {structlog_config-0.5.0 → structlog_config-0.7.0}/structlog_config/constants.py +0 -0
- {structlog_config-0.5.0 → structlog_config-0.7.0}/structlog_config/env_config.py +0 -0
- {structlog_config-0.5.0 → structlog_config-0.7.0}/structlog_config/environments.py +0 -0
- {structlog_config-0.5.0 → structlog_config-0.7.0}/structlog_config/levels.py +0 -0
- {structlog_config-0.5.0 → structlog_config-0.7.0}/structlog_config/warnings.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: structlog-config
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: A comprehensive structlog configuration with sensible defaults for development and production environments, featuring context management, exception formatting, and path prettification.
|
|
5
5
|
Keywords: logging,structlog,json-logging,structured-logging
|
|
6
6
|
Author: Michael Bianco
|
|
@@ -9,8 +9,10 @@ Requires-Dist: orjson>=3.10.15
|
|
|
9
9
|
Requires-Dist: python-decouple-typed>=3.11.0
|
|
10
10
|
Requires-Dist: python-ipware>=3.0.0
|
|
11
11
|
Requires-Dist: structlog>=25.2.0
|
|
12
|
-
Requires-
|
|
12
|
+
Requires-Dist: fastapi-ipware>=0.1.0 ; extra == 'fastapi'
|
|
13
|
+
Requires-Python: >=3.11
|
|
13
14
|
Project-URL: Repository, https://github.com/iloveitaly/structlog-config
|
|
15
|
+
Provides-Extra: fastapi
|
|
14
16
|
Description-Content-Type: text/markdown
|
|
15
17
|
|
|
16
18
|
# Opinionated Defaults for Structlog
|
|
@@ -29,6 +31,18 @@ Here are the main goals:
|
|
|
29
31
|
* Ability to log level and output (i.e. file path) *by logger* for easy development debugging
|
|
30
32
|
* If you are using fastapi, structured logging for access logs
|
|
31
33
|
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install structlog-config
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Or with [uv](https://docs.astral.sh/uv/):
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
uv add structlog-config
|
|
44
|
+
```
|
|
45
|
+
|
|
32
46
|
## Usage
|
|
33
47
|
|
|
34
48
|
```python
|
|
@@ -106,8 +120,68 @@ For example, if you wanted to [mimic `OPENAI_LOG` functionality](https://github.
|
|
|
106
120
|
* `LOG_LEVEL_HTTPX=DEBUG`
|
|
107
121
|
* `LOG_PATH_HTTPX=tmp/openai.log`
|
|
108
122
|
|
|
123
|
+
## Custom Formatters
|
|
124
|
+
|
|
125
|
+
This package includes several custom formatters that automatically clean up log output:
|
|
126
|
+
|
|
127
|
+
### Path Prettifier
|
|
128
|
+
|
|
129
|
+
Automatically formats `pathlib.Path` and `PosixPath` objects to show relative paths when possible, removing the wrapper class names:
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
from pathlib import Path
|
|
133
|
+
log.info("Processing file", file_path=Path.cwd() / "data" / "users.csv")
|
|
134
|
+
# Output: file_path=data/users.csv (instead of PosixPath('/home/user/data/users.csv'))
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Whenever Datetime Formatter
|
|
138
|
+
|
|
139
|
+
**Note:** Requires `pip install whenever` to be installed.
|
|
140
|
+
|
|
141
|
+
Formats [whenever](https://github.com/ariebovenberg/whenever) datetime objects without their class wrappers for cleaner output:
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
from whenever import ZonedDateTime
|
|
145
|
+
|
|
146
|
+
log.info("Event scheduled", event_time=ZonedDateTime(2025, 11, 2, 0, 0, 0, tz="UTC"))
|
|
147
|
+
# Output: event_time=2025-11-02T00:00:00+00:00[UTC]
|
|
148
|
+
# Instead of: event_time=ZonedDateTime("2025-11-02T00:00:00+00:00[UTC]")
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Supports all whenever datetime types: `ZonedDateTime`, `Instant`, `LocalDateTime`, `PlainDateTime`, etc.
|
|
152
|
+
|
|
153
|
+
### ActiveModel Object Formatter
|
|
154
|
+
|
|
155
|
+
**Note:** Requires `pip install activemodel` and `pip install typeid-python` to be installed.
|
|
156
|
+
|
|
157
|
+
Automatically converts [ActiveModel](https://github.com/iloveitaly/activemodel) BaseModel instances to their ID representation and TypeID objects to strings:
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
from activemodel import BaseModel
|
|
161
|
+
|
|
162
|
+
user = User(id="user_123", name="Alice")
|
|
163
|
+
log.info("User action", user=user)
|
|
164
|
+
# Output: user_id=user_123 (instead of full object representation)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### FastAPI Context
|
|
168
|
+
|
|
169
|
+
**Note:** Requires `pip install starlette-context` to be installed.
|
|
170
|
+
|
|
171
|
+
Automatically includes all context data from [starlette-context](https://github.com/tomwojcik/starlette-context) in your logs, useful for request tracing:
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
# Context data (request_id, correlation_id, etc.) automatically included in all logs
|
|
175
|
+
log.info("Processing request")
|
|
176
|
+
# Output includes: request_id=abc-123 correlation_id=xyz-789 ...
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
All formatters are optional and automatically enabled when their respective dependencies are installed. They work seamlessly in both development (console) and production (JSON) logging modes.
|
|
180
|
+
|
|
109
181
|
## FastAPI Access Logger
|
|
110
182
|
|
|
183
|
+
**Note:** Requires `pip install structlog-config[fastapi]` for FastAPI dependencies.
|
|
184
|
+
|
|
111
185
|
Structured, simple access log with request timing to replace the default fastapi access log. Why?
|
|
112
186
|
|
|
113
187
|
1. It's less verbose
|
|
@@ -119,6 +193,74 @@ Here's how to use it:
|
|
|
119
193
|
1. [Disable fastapi's default logging.](https://github.com/iloveitaly/python-starter-template/blob/f54cb47d8d104987f2e4a668f9045a62e0d6818a/main.py#L55-L56)
|
|
120
194
|
2. [Add the middleware to your FastAPI app.](https://github.com/iloveitaly/python-starter-template/blob/f54cb47d8d104987f2e4a668f9045a62e0d6818a/app/routes/middleware/__init__.py#L63-L65)
|
|
121
195
|
|
|
196
|
+
## Pytest Plugin: Capture Logs on Failure
|
|
197
|
+
|
|
198
|
+
A pytest plugin that captures logs per-test and displays them only when tests fail. This keeps your test output clean while ensuring you have all the debugging information you need when something goes wrong.
|
|
199
|
+
|
|
200
|
+
### Features
|
|
201
|
+
|
|
202
|
+
- Only shows logs for failing tests (keeps output clean)
|
|
203
|
+
- Captures logs from all test phases (setup, call, teardown)
|
|
204
|
+
- Unique log file per test
|
|
205
|
+
- Optional persistent log storage for debugging
|
|
206
|
+
- Automatically handles `PYTHON_LOG_PATH` environment variable
|
|
207
|
+
|
|
208
|
+
### Usage
|
|
209
|
+
|
|
210
|
+
Enable the plugin with the `--capture-logs-on-fail` flag:
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
pytest --capture-logs-on-fail
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Or enable it permanently in `pytest.ini` or `pyproject.toml`:
|
|
217
|
+
|
|
218
|
+
```toml
|
|
219
|
+
[tool.pytest.ini_options]
|
|
220
|
+
addopts = ["--capture-logs-on-fail"]
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Persist Logs to Directory
|
|
224
|
+
|
|
225
|
+
To keep all test logs for later inspection (useful for CI/CD debugging):
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
pytest --capture-logs-dir=./test-logs
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
This creates a log file for each test and disables automatic cleanup.
|
|
232
|
+
|
|
233
|
+
### How It Works
|
|
234
|
+
|
|
235
|
+
1. Sets `PYTHON_LOG_PATH` environment variable to a unique temp file for each test
|
|
236
|
+
2. Your application logs (via `configure_logger()`) write to this file
|
|
237
|
+
3. On test failure, the plugin prints the captured logs to stdout
|
|
238
|
+
4. Log files are cleaned up after the test session (unless `--capture-logs-dir` is used)
|
|
239
|
+
|
|
240
|
+
### Example Output
|
|
241
|
+
|
|
242
|
+
When a test fails, you'll see:
|
|
243
|
+
|
|
244
|
+
```
|
|
245
|
+
FAILED tests/test_user.py::test_user_login
|
|
246
|
+
|
|
247
|
+
--- Captured logs for failed test (call): tests/test_user.py::test_user_login ---
|
|
248
|
+
2025-11-01 18:30:00 [info] User login started user_id=123
|
|
249
|
+
2025-11-01 18:30:01 [error] Database connection failed timeout=5.0
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
For passing tests, no log output is shown, keeping your test output clean and focused.
|
|
253
|
+
|
|
254
|
+
## Beautiful Traceback Support
|
|
255
|
+
|
|
256
|
+
Optional support for [beautiful-traceback](https://github.com/iloveitaly/beautiful-traceback) provides enhanced exception formatting with improved readability, smart coloring, path aliasing (e.g., `<pwd>`, `<site>`), and better alignment. Automatically activates when installed:
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
uv add beautiful-traceback --group dev
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
No configuration needed - just install and `configure_logger()` will use it automatically.
|
|
263
|
+
|
|
122
264
|
## iPython
|
|
123
265
|
|
|
124
266
|
Often it's helpful to update logging level within an iPython session. You can do this and make sure all loggers pick up on it.
|
|
@@ -14,6 +14,18 @@ Here are the main goals:
|
|
|
14
14
|
* Ability to log level and output (i.e. file path) *by logger* for easy development debugging
|
|
15
15
|
* If you are using fastapi, structured logging for access logs
|
|
16
16
|
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install structlog-config
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Or with [uv](https://docs.astral.sh/uv/):
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
uv add structlog-config
|
|
27
|
+
```
|
|
28
|
+
|
|
17
29
|
## Usage
|
|
18
30
|
|
|
19
31
|
```python
|
|
@@ -91,8 +103,68 @@ For example, if you wanted to [mimic `OPENAI_LOG` functionality](https://github.
|
|
|
91
103
|
* `LOG_LEVEL_HTTPX=DEBUG`
|
|
92
104
|
* `LOG_PATH_HTTPX=tmp/openai.log`
|
|
93
105
|
|
|
106
|
+
## Custom Formatters
|
|
107
|
+
|
|
108
|
+
This package includes several custom formatters that automatically clean up log output:
|
|
109
|
+
|
|
110
|
+
### Path Prettifier
|
|
111
|
+
|
|
112
|
+
Automatically formats `pathlib.Path` and `PosixPath` objects to show relative paths when possible, removing the wrapper class names:
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
from pathlib import Path
|
|
116
|
+
log.info("Processing file", file_path=Path.cwd() / "data" / "users.csv")
|
|
117
|
+
# Output: file_path=data/users.csv (instead of PosixPath('/home/user/data/users.csv'))
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Whenever Datetime Formatter
|
|
121
|
+
|
|
122
|
+
**Note:** Requires `pip install whenever` to be installed.
|
|
123
|
+
|
|
124
|
+
Formats [whenever](https://github.com/ariebovenberg/whenever) datetime objects without their class wrappers for cleaner output:
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from whenever import ZonedDateTime
|
|
128
|
+
|
|
129
|
+
log.info("Event scheduled", event_time=ZonedDateTime(2025, 11, 2, 0, 0, 0, tz="UTC"))
|
|
130
|
+
# Output: event_time=2025-11-02T00:00:00+00:00[UTC]
|
|
131
|
+
# Instead of: event_time=ZonedDateTime("2025-11-02T00:00:00+00:00[UTC]")
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Supports all whenever datetime types: `ZonedDateTime`, `Instant`, `LocalDateTime`, `PlainDateTime`, etc.
|
|
135
|
+
|
|
136
|
+
### ActiveModel Object Formatter
|
|
137
|
+
|
|
138
|
+
**Note:** Requires `pip install activemodel` and `pip install typeid-python` to be installed.
|
|
139
|
+
|
|
140
|
+
Automatically converts [ActiveModel](https://github.com/iloveitaly/activemodel) BaseModel instances to their ID representation and TypeID objects to strings:
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from activemodel import BaseModel
|
|
144
|
+
|
|
145
|
+
user = User(id="user_123", name="Alice")
|
|
146
|
+
log.info("User action", user=user)
|
|
147
|
+
# Output: user_id=user_123 (instead of full object representation)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### FastAPI Context
|
|
151
|
+
|
|
152
|
+
**Note:** Requires `pip install starlette-context` to be installed.
|
|
153
|
+
|
|
154
|
+
Automatically includes all context data from [starlette-context](https://github.com/tomwojcik/starlette-context) in your logs, useful for request tracing:
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
# Context data (request_id, correlation_id, etc.) automatically included in all logs
|
|
158
|
+
log.info("Processing request")
|
|
159
|
+
# Output includes: request_id=abc-123 correlation_id=xyz-789 ...
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
All formatters are optional and automatically enabled when their respective dependencies are installed. They work seamlessly in both development (console) and production (JSON) logging modes.
|
|
163
|
+
|
|
94
164
|
## FastAPI Access Logger
|
|
95
165
|
|
|
166
|
+
**Note:** Requires `pip install structlog-config[fastapi]` for FastAPI dependencies.
|
|
167
|
+
|
|
96
168
|
Structured, simple access log with request timing to replace the default fastapi access log. Why?
|
|
97
169
|
|
|
98
170
|
1. It's less verbose
|
|
@@ -104,6 +176,74 @@ Here's how to use it:
|
|
|
104
176
|
1. [Disable fastapi's default logging.](https://github.com/iloveitaly/python-starter-template/blob/f54cb47d8d104987f2e4a668f9045a62e0d6818a/main.py#L55-L56)
|
|
105
177
|
2. [Add the middleware to your FastAPI app.](https://github.com/iloveitaly/python-starter-template/blob/f54cb47d8d104987f2e4a668f9045a62e0d6818a/app/routes/middleware/__init__.py#L63-L65)
|
|
106
178
|
|
|
179
|
+
## Pytest Plugin: Capture Logs on Failure
|
|
180
|
+
|
|
181
|
+
A pytest plugin that captures logs per-test and displays them only when tests fail. This keeps your test output clean while ensuring you have all the debugging information you need when something goes wrong.
|
|
182
|
+
|
|
183
|
+
### Features
|
|
184
|
+
|
|
185
|
+
- Only shows logs for failing tests (keeps output clean)
|
|
186
|
+
- Captures logs from all test phases (setup, call, teardown)
|
|
187
|
+
- Unique log file per test
|
|
188
|
+
- Optional persistent log storage for debugging
|
|
189
|
+
- Automatically handles `PYTHON_LOG_PATH` environment variable
|
|
190
|
+
|
|
191
|
+
### Usage
|
|
192
|
+
|
|
193
|
+
Enable the plugin with the `--capture-logs-on-fail` flag:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
pytest --capture-logs-on-fail
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Or enable it permanently in `pytest.ini` or `pyproject.toml`:
|
|
200
|
+
|
|
201
|
+
```toml
|
|
202
|
+
[tool.pytest.ini_options]
|
|
203
|
+
addopts = ["--capture-logs-on-fail"]
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Persist Logs to Directory
|
|
207
|
+
|
|
208
|
+
To keep all test logs for later inspection (useful for CI/CD debugging):
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
pytest --capture-logs-dir=./test-logs
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
This creates a log file for each test and disables automatic cleanup.
|
|
215
|
+
|
|
216
|
+
### How It Works
|
|
217
|
+
|
|
218
|
+
1. Sets `PYTHON_LOG_PATH` environment variable to a unique temp file for each test
|
|
219
|
+
2. Your application logs (via `configure_logger()`) write to this file
|
|
220
|
+
3. On test failure, the plugin prints the captured logs to stdout
|
|
221
|
+
4. Log files are cleaned up after the test session (unless `--capture-logs-dir` is used)
|
|
222
|
+
|
|
223
|
+
### Example Output
|
|
224
|
+
|
|
225
|
+
When a test fails, you'll see:
|
|
226
|
+
|
|
227
|
+
```
|
|
228
|
+
FAILED tests/test_user.py::test_user_login
|
|
229
|
+
|
|
230
|
+
--- Captured logs for failed test (call): tests/test_user.py::test_user_login ---
|
|
231
|
+
2025-11-01 18:30:00 [info] User login started user_id=123
|
|
232
|
+
2025-11-01 18:30:01 [error] Database connection failed timeout=5.0
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
For passing tests, no log output is shown, keeping your test output clean and focused.
|
|
236
|
+
|
|
237
|
+
## Beautiful Traceback Support
|
|
238
|
+
|
|
239
|
+
Optional support for [beautiful-traceback](https://github.com/iloveitaly/beautiful-traceback) provides enhanced exception formatting with improved readability, smart coloring, path aliasing (e.g., `<pwd>`, `<site>`), and better alignment. Automatically activates when installed:
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
uv add beautiful-traceback --group dev
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
No configuration needed - just install and `configure_logger()` will use it automatically.
|
|
246
|
+
|
|
107
247
|
## iPython
|
|
108
248
|
|
|
109
249
|
Often it's helpful to update logging level within an iPython session. You can do this and make sure all loggers pick up on it.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "structlog-config"
|
|
3
|
+
version = "0.7.0"
|
|
4
|
+
description = "A comprehensive structlog configuration with sensible defaults for development and production environments, featuring context management, exception formatting, and path prettification."
|
|
5
|
+
keywords = ["logging", "structlog", "json-logging", "structured-logging"]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
requires-python = ">=3.11"
|
|
8
|
+
dependencies = [
|
|
9
|
+
"orjson>=3.10.15",
|
|
10
|
+
"python-decouple-typed>=3.11.0",
|
|
11
|
+
"python-ipware>=3.0.0",
|
|
12
|
+
"structlog>=25.2.0",
|
|
13
|
+
]
|
|
14
|
+
urls = { "Repository" = "https://github.com/iloveitaly/structlog-config" }
|
|
15
|
+
authors = [{ name = "Michael Bianco", email = "mike@mikebian.co" }]
|
|
16
|
+
|
|
17
|
+
[project.optional-dependencies]
|
|
18
|
+
fastapi = ["fastapi-ipware>=0.1.0"]
|
|
19
|
+
|
|
20
|
+
[build-system]
|
|
21
|
+
requires = ["uv_build>=0.8.11,<0.9.0"]
|
|
22
|
+
build-backend = "uv_build"
|
|
23
|
+
|
|
24
|
+
[tool.uv.build-backend]
|
|
25
|
+
# avoids the src/ directory structure
|
|
26
|
+
module-root = ""
|
|
27
|
+
|
|
28
|
+
[dependency-groups]
|
|
29
|
+
dev = [
|
|
30
|
+
"beautiful-traceback>=0.1.0",
|
|
31
|
+
"fastapi>=0.115.12",
|
|
32
|
+
"httpx>=0.28.1",
|
|
33
|
+
"pytest>=8.3.3",
|
|
34
|
+
"fastapi_ipware>=0.1.0",
|
|
35
|
+
"whenever>=0.9.3",
|
|
36
|
+
]
|
|
@@ -11,9 +11,10 @@ from structlog.typing import FilteringBoundLogger
|
|
|
11
11
|
|
|
12
12
|
from structlog_config.formatters import (
|
|
13
13
|
PathPrettifier,
|
|
14
|
+
WheneverFormatter,
|
|
14
15
|
add_fastapi_context,
|
|
16
|
+
beautiful_traceback_exception_formatter,
|
|
15
17
|
logger_name,
|
|
16
|
-
pretty_traceback_exception_formatter,
|
|
17
18
|
simplify_activemodel_objects,
|
|
18
19
|
)
|
|
19
20
|
|
|
@@ -45,8 +46,7 @@ def log_processors_for_mode(json_logger: bool) -> list[structlog.types.Processor
|
|
|
45
46
|
)
|
|
46
47
|
|
|
47
48
|
return [
|
|
48
|
-
#
|
|
49
|
-
structlog.processors.format_exc_info,
|
|
49
|
+
# omit `structlog.processors.format_exc_info` so we can use structured logging for exceptions
|
|
50
50
|
# simple, short exception rendering in prod since sentry is in place
|
|
51
51
|
# https://www.structlog.org/en/stable/exceptions.html this is a customized version of dict_tracebacks
|
|
52
52
|
ExceptionRenderer(
|
|
@@ -65,8 +65,8 @@ def log_processors_for_mode(json_logger: bool) -> list[structlog.types.Processor
|
|
|
65
65
|
return [
|
|
66
66
|
structlog.dev.ConsoleRenderer(
|
|
67
67
|
colors=not NO_COLOR,
|
|
68
|
-
exception_formatter=
|
|
69
|
-
if packages.
|
|
68
|
+
exception_formatter=beautiful_traceback_exception_formatter
|
|
69
|
+
if packages.beautiful_traceback
|
|
70
70
|
else structlog.dev.default_exception_formatter,
|
|
71
71
|
)
|
|
72
72
|
]
|
|
@@ -86,6 +86,7 @@ def get_default_processors(json_logger) -> list[structlog.types.Processor]:
|
|
|
86
86
|
if packages.activemodel and packages.typeid
|
|
87
87
|
else None,
|
|
88
88
|
PathPrettifier(),
|
|
89
|
+
WheneverFormatter() if packages.whenever else None,
|
|
89
90
|
structlog.processors.TimeStamper(fmt="iso", utc=True),
|
|
90
91
|
# add `stack_info=True` to a log and get a `stack` attached to the log
|
|
91
92
|
structlog.processors.StackInfoRenderer(),
|
|
@@ -7,7 +7,7 @@ from urllib.parse import quote
|
|
|
7
7
|
|
|
8
8
|
import structlog
|
|
9
9
|
from fastapi import FastAPI
|
|
10
|
-
from
|
|
10
|
+
from fastapi_ipware import FastAPIIpWare
|
|
11
11
|
from starlette.middleware.base import RequestResponseEndpoint
|
|
12
12
|
from starlette.requests import Request
|
|
13
13
|
from starlette.responses import Response
|
|
@@ -15,9 +15,8 @@ from starlette.routing import Match, Mount
|
|
|
15
15
|
from starlette.types import Scope
|
|
16
16
|
from starlette.websockets import WebSocket
|
|
17
17
|
|
|
18
|
-
# should name this access "access_log" or something
|
|
19
18
|
log = structlog.get_logger()
|
|
20
|
-
|
|
19
|
+
ipware = FastAPIIpWare()
|
|
21
20
|
|
|
22
21
|
|
|
23
22
|
def get_route_name(app: FastAPI, scope: Scope, prefix: str = "") -> str:
|
|
@@ -59,35 +58,17 @@ def client_ip_from_request(request: Request | WebSocket) -> str | None:
|
|
|
59
58
|
"""
|
|
60
59
|
Get the client IP address from the request.
|
|
61
60
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
Uses ipware library to properly extract client IP from various proxy headers.
|
|
61
|
+
Uses fastapi-ipware library to properly extract client IP from various proxy headers.
|
|
65
62
|
Fallback to direct client connection if no proxy headers found.
|
|
66
63
|
"""
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
# TODO this seems really inefficient, we should just rewrite the ipware into this repo :/
|
|
70
|
-
# Convert Starlette headers to format expected by ipware (HTTP_ prefixed)
|
|
71
|
-
# ipware expects headers in WSGI/Django-style meta format where HTTP headers
|
|
72
|
-
# are prefixed with "HTTP_" and dashes become underscores.
|
|
73
|
-
# See: https://github.com/un33k/python-ipware/blob/main/python_ipware/python_ipware.py#L33-L40
|
|
74
|
-
meta_dict = {}
|
|
75
|
-
for name, value in headers.items():
|
|
76
|
-
# Convert header name to HTTP_ prefixed format
|
|
77
|
-
meta_key = f"HTTP_{name.upper().replace('-', '_')}"
|
|
78
|
-
meta_dict[meta_key] = value
|
|
79
|
-
|
|
80
|
-
# Use ipware to extract IP from headers
|
|
81
|
-
ip, trusted_route = ipw.get_client_ip(meta=meta_dict)
|
|
64
|
+
ip, trusted_route = ipware.get_client_ip_from_request(request)
|
|
82
65
|
if ip:
|
|
83
66
|
log.debug(
|
|
84
67
|
"extracted client IP from headers", ip=ip, trusted_route=trusted_route
|
|
85
68
|
)
|
|
86
69
|
return str(ip)
|
|
87
70
|
|
|
88
|
-
# Fallback to direct client connection
|
|
89
71
|
host = request.client.host if request.client else None
|
|
90
|
-
|
|
91
72
|
return host
|
|
92
73
|
|
|
93
74
|
|
|
@@ -101,16 +82,8 @@ def is_static_assets_request(scope: Scope) -> bool:
|
|
|
101
82
|
Returns:
|
|
102
83
|
bool: True if the request is for static assets, False otherwise.
|
|
103
84
|
"""
|
|
104
|
-
return (
|
|
105
|
-
|
|
106
|
-
or scope["path"].endswith(".js")
|
|
107
|
-
# .map files are attempted when devtools are enabled
|
|
108
|
-
or scope["path"].endswith(".js.map")
|
|
109
|
-
or scope["path"].endswith(".ico")
|
|
110
|
-
or scope["path"].endswith(".png")
|
|
111
|
-
or scope["path"].endswith(".jpg")
|
|
112
|
-
or scope["path"].endswith(".jpeg")
|
|
113
|
-
or scope["path"].endswith(".gif")
|
|
85
|
+
return scope["path"].endswith(
|
|
86
|
+
(".css", ".js", ".js.map", ".ico", ".png", ".jpg", ".jpeg", ".gif", ".webp")
|
|
114
87
|
)
|
|
115
88
|
|
|
116
89
|
|
|
@@ -64,17 +64,17 @@ def logger_name(logger: Any, method_name: Any, event_dict: EventDict) -> EventDi
|
|
|
64
64
|
return event_dict
|
|
65
65
|
|
|
66
66
|
|
|
67
|
-
def
|
|
67
|
+
def beautiful_traceback_exception_formatter(sio: TextIO, exc_info: ExcInfo) -> None:
|
|
68
68
|
"""
|
|
69
69
|
By default, rich and then better-exceptions is used to render exceptions when a ConsoleRenderer is used.
|
|
70
70
|
|
|
71
|
-
I prefer
|
|
71
|
+
I prefer beautiful-traceback, so I've added a custom processor to use it.
|
|
72
72
|
|
|
73
73
|
https://github.com/hynek/structlog/blob/66e22d261bf493ad2084009ec97c51832fdbb0b9/src/structlog/dev.py#L412
|
|
74
74
|
"""
|
|
75
75
|
|
|
76
76
|
# only available in dev
|
|
77
|
-
from
|
|
77
|
+
from beautiful_traceback.formatting import exc_to_traceback_str
|
|
78
78
|
|
|
79
79
|
_, exc_value, traceback = exc_info
|
|
80
80
|
# TODO support local_stack_only env var support
|
|
@@ -150,6 +150,26 @@ class RenameField:
|
|
|
150
150
|
return event_dict
|
|
151
151
|
|
|
152
152
|
|
|
153
|
+
class WheneverFormatter:
|
|
154
|
+
"""A processor for formatting whenever datetime objects.
|
|
155
|
+
|
|
156
|
+
Changes all whenever datetime objects (ZonedDateTime, Instant, PlainDateTime, etc.)
|
|
157
|
+
from their repr() format (e.g., ZonedDateTime("2025-11-02 00:00:00+00:00[UTC]"))
|
|
158
|
+
to their string format (e.g., 2025-11-02 00:00:00+00:00[UTC]).
|
|
159
|
+
|
|
160
|
+
This provides cleaner log output without the class wrapper.
|
|
161
|
+
"""
|
|
162
|
+
|
|
163
|
+
def __call__(self, _, __, event_dict):
|
|
164
|
+
for key, value in event_dict.items():
|
|
165
|
+
# Check if the value has the _pywhenever module attribute
|
|
166
|
+
# This is a reliable way to detect whenever types without importing them
|
|
167
|
+
if hasattr(value, "__module__") and value.__module__.startswith("whenever"):
|
|
168
|
+
event_dict[key] = str(value)
|
|
169
|
+
|
|
170
|
+
return event_dict
|
|
171
|
+
|
|
172
|
+
|
|
153
173
|
def add_fastapi_context(
|
|
154
174
|
logger: logging.Logger,
|
|
155
175
|
method_name: str,
|
|
@@ -23,11 +23,16 @@ except ImportError:
|
|
|
23
23
|
typeid = None
|
|
24
24
|
|
|
25
25
|
try:
|
|
26
|
-
import
|
|
26
|
+
import beautiful_traceback
|
|
27
27
|
except ImportError:
|
|
28
|
-
|
|
28
|
+
beautiful_traceback = None
|
|
29
29
|
|
|
30
30
|
try:
|
|
31
31
|
import starlette_context
|
|
32
32
|
except ImportError:
|
|
33
33
|
starlette_context = None
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
import whenever
|
|
37
|
+
except ImportError:
|
|
38
|
+
whenever = None
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pytest plugin for capturing and displaying logs only on test failures.
|
|
3
|
+
|
|
4
|
+
This plugin integrates with structlog-config's file logging to capture logs per-test
|
|
5
|
+
and display them only when tests fail, keeping output clean for passing tests.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
1. Install the plugin (automatically registered via entry point):
|
|
9
|
+
pip install structlog-config[fastapi]
|
|
10
|
+
|
|
11
|
+
2. Enable in pytest.ini or pyproject.toml:
|
|
12
|
+
[tool.pytest.ini_options]
|
|
13
|
+
addopts = ["--capture-logs-on-fail"]
|
|
14
|
+
|
|
15
|
+
Or enable for a single test run:
|
|
16
|
+
pytest --capture-logs-on-fail
|
|
17
|
+
|
|
18
|
+
3. Optional: Persist all logs to a directory:
|
|
19
|
+
pytest --capture-logs-dir=/path/to/logs
|
|
20
|
+
|
|
21
|
+
How it works:
|
|
22
|
+
- Sets PYTHON_LOG_PATH to a unique temp file for each test
|
|
23
|
+
- Logs are written to /tmp/<project-name>-pytest-logs-*/test_name.log
|
|
24
|
+
- On test failure, prints captured logs to stdout
|
|
25
|
+
- Cleans up temp files after each test (unless --capture-logs-dir is set)
|
|
26
|
+
- Automatically disabled if PYTHON_LOG_PATH is already set
|
|
27
|
+
|
|
28
|
+
Example output on failure:
|
|
29
|
+
--- Captured logs for failed test: tests/test_foo.py::test_bar ---
|
|
30
|
+
2025-10-31 23:30:00 [info] Starting test
|
|
31
|
+
2025-10-31 23:30:01 [error] Something went wrong
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
import logging
|
|
35
|
+
import os
|
|
36
|
+
import re
|
|
37
|
+
import shutil
|
|
38
|
+
import tempfile
|
|
39
|
+
from pathlib import Path
|
|
40
|
+
from typing import Generator
|
|
41
|
+
|
|
42
|
+
import pytest
|
|
43
|
+
|
|
44
|
+
logger = logging.getLogger(__name__)
|
|
45
|
+
|
|
46
|
+
PLUGIN_KEY = pytest.StashKey[dict]()
|
|
47
|
+
SESSION_TMPDIR_KEY = "session_tmpdir"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def sanitize_filename(name: str) -> str:
|
|
51
|
+
"""Replace non-filename-safe characters with underscores.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
name: The filename to sanitize (typically a pytest nodeid).
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
A filesystem-safe filename string.
|
|
58
|
+
"""
|
|
59
|
+
return re.sub(r"[^A-Za-z0-9_.-]", "_", name)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def pytest_addoption(parser: pytest.Parser) -> None:
|
|
63
|
+
"""Register the --capture-logs-on-fail command line option.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
parser: The pytest parser to add options to.
|
|
67
|
+
"""
|
|
68
|
+
parser.addoption(
|
|
69
|
+
"--capture-logs-on-fail",
|
|
70
|
+
action="store_true",
|
|
71
|
+
default=False,
|
|
72
|
+
help="Capture logs to a temp file and dump them to stdout on test failure.",
|
|
73
|
+
)
|
|
74
|
+
parser.addoption(
|
|
75
|
+
"--capture-logs-dir",
|
|
76
|
+
action="store",
|
|
77
|
+
default=None,
|
|
78
|
+
help="Directory to persist all test logs (disables automatic cleanup).",
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@pytest.hookimpl(tryfirst=True)
|
|
83
|
+
def pytest_configure(config: pytest.Config) -> None:
|
|
84
|
+
"""Configure the plugin at pytest startup.
|
|
85
|
+
|
|
86
|
+
Stores configuration state on the config object for use by fixtures and hooks.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
config: The pytest config object.
|
|
90
|
+
"""
|
|
91
|
+
logs_dir = config.getoption("--capture-logs-dir")
|
|
92
|
+
enabled = config.getoption("--capture-logs-on-fail") or logs_dir is not None
|
|
93
|
+
|
|
94
|
+
plugin_config = {
|
|
95
|
+
"enabled": enabled,
|
|
96
|
+
"logs_dir": logs_dir,
|
|
97
|
+
"project_name": os.path.basename(str(config.rootdir)),
|
|
98
|
+
}
|
|
99
|
+
config.stash[PLUGIN_KEY] = plugin_config
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@pytest.hookimpl(tryfirst=True)
|
|
103
|
+
def pytest_sessionstart(session: pytest.Session) -> None:
|
|
104
|
+
"""Create a session-level temp directory for log files.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
session: The pytest session object.
|
|
108
|
+
"""
|
|
109
|
+
config = session.config
|
|
110
|
+
plugin_config = config.stash.get(PLUGIN_KEY, {})
|
|
111
|
+
|
|
112
|
+
if not plugin_config.get("enabled"):
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
logs_dir = plugin_config.get("logs_dir")
|
|
116
|
+
if logs_dir:
|
|
117
|
+
tmpdir = Path(logs_dir)
|
|
118
|
+
tmpdir.mkdir(parents=True, exist_ok=True)
|
|
119
|
+
else:
|
|
120
|
+
project_name = plugin_config.get("project_name", "pytest")
|
|
121
|
+
tmpdir = Path(tempfile.mkdtemp(prefix=f"{project_name}-pytest-logs-"))
|
|
122
|
+
|
|
123
|
+
plugin_config[SESSION_TMPDIR_KEY] = tmpdir
|
|
124
|
+
config.stash[PLUGIN_KEY] = plugin_config
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@pytest.hookimpl(trylast=True)
|
|
128
|
+
def pytest_sessionfinish(session: pytest.Session) -> None:
|
|
129
|
+
"""Clean up session-level temp directory unless --capture-logs-dir was used.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
session: The pytest session object.
|
|
133
|
+
"""
|
|
134
|
+
config = session.config
|
|
135
|
+
plugin_config = config.stash.get(PLUGIN_KEY, {})
|
|
136
|
+
|
|
137
|
+
if not plugin_config.get("enabled"):
|
|
138
|
+
return
|
|
139
|
+
|
|
140
|
+
logs_dir = plugin_config.get("logs_dir")
|
|
141
|
+
tmpdir = plugin_config.get(SESSION_TMPDIR_KEY)
|
|
142
|
+
|
|
143
|
+
if tmpdir and not logs_dir:
|
|
144
|
+
shutil.rmtree(tmpdir, ignore_errors=True)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@pytest.fixture(autouse=True)
|
|
148
|
+
def capture_logs_on_fail(request: pytest.FixtureRequest) -> Generator[None, None, None]:
|
|
149
|
+
"""Set up per-test log capture to a temporary file.
|
|
150
|
+
|
|
151
|
+
This fixture runs automatically for every test when --capture-logs-on-fail is enabled.
|
|
152
|
+
It sets PYTHON_LOG_PATH to redirect logs to a unique temp file, then cleans up after.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
request: The pytest request fixture providing test context.
|
|
156
|
+
|
|
157
|
+
Yields:
|
|
158
|
+
Control back to the test, then handles cleanup after test completion.
|
|
159
|
+
"""
|
|
160
|
+
config = request.config
|
|
161
|
+
plugin_config = config.stash.get(PLUGIN_KEY, {})
|
|
162
|
+
|
|
163
|
+
if not plugin_config.get("enabled"):
|
|
164
|
+
yield
|
|
165
|
+
return
|
|
166
|
+
|
|
167
|
+
if "PYTHON_LOG_PATH" in os.environ:
|
|
168
|
+
logger.warning(
|
|
169
|
+
"PYTHON_LOG_PATH is already set; pytest-capture-logs-on-fail plugin is disabled for this test."
|
|
170
|
+
)
|
|
171
|
+
yield
|
|
172
|
+
return
|
|
173
|
+
|
|
174
|
+
tmpdir = plugin_config.get(SESSION_TMPDIR_KEY)
|
|
175
|
+
if not tmpdir:
|
|
176
|
+
logger.warning("Session temp directory not initialized")
|
|
177
|
+
yield
|
|
178
|
+
return
|
|
179
|
+
|
|
180
|
+
test_name = sanitize_filename(request.node.nodeid)
|
|
181
|
+
log_file = tmpdir / f"{test_name}.log"
|
|
182
|
+
|
|
183
|
+
original_log_path = os.environ.get("PYTHON_LOG_PATH")
|
|
184
|
+
os.environ["PYTHON_LOG_PATH"] = str(log_file)
|
|
185
|
+
|
|
186
|
+
logger.info(f"Logs for test '{request.node.nodeid}' will be stored at: {log_file}")
|
|
187
|
+
|
|
188
|
+
yield
|
|
189
|
+
|
|
190
|
+
setattr(request.node, "_pytest_log_file", str(log_file))
|
|
191
|
+
|
|
192
|
+
if original_log_path is not None:
|
|
193
|
+
os.environ["PYTHON_LOG_PATH"] = original_log_path
|
|
194
|
+
else:
|
|
195
|
+
del os.environ["PYTHON_LOG_PATH"]
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def pytest_runtest_makereport(item: pytest.Item, call: pytest.CallInfo) -> None:
|
|
199
|
+
"""Hook called after each test phase to create test reports.
|
|
200
|
+
|
|
201
|
+
On test failure, reads and prints the captured log file to stdout.
|
|
202
|
+
Handles failures in setup, call, and teardown phases.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
item: The test item being reported on.
|
|
206
|
+
call: The call object containing execution info and any exception.
|
|
207
|
+
"""
|
|
208
|
+
config = item.config
|
|
209
|
+
plugin_config = config.stash.get(PLUGIN_KEY, {})
|
|
210
|
+
|
|
211
|
+
if not plugin_config.get("enabled"):
|
|
212
|
+
return
|
|
213
|
+
|
|
214
|
+
if call.excinfo is not None:
|
|
215
|
+
log_file = getattr(item, "_pytest_log_file", None)
|
|
216
|
+
if log_file and os.path.exists(log_file):
|
|
217
|
+
with open(log_file, "r") as f:
|
|
218
|
+
logs = f.read()
|
|
219
|
+
|
|
220
|
+
if logs.strip():
|
|
221
|
+
phase = call.when
|
|
222
|
+
print(f"\n--- Captured logs for failed test ({phase}): {item.nodeid} ---\n{logs}\n")
|
|
@@ -11,7 +11,6 @@ from decouple import config
|
|
|
11
11
|
|
|
12
12
|
from .constants import PYTHONASYNCIODEBUG
|
|
13
13
|
from .env_config import get_custom_logger_config
|
|
14
|
-
from .environments import is_production, is_staging
|
|
15
14
|
from .levels import (
|
|
16
15
|
compare_log_levels,
|
|
17
16
|
get_environment_log_level_as_string,
|
|
@@ -49,14 +48,18 @@ def redirect_stdlib_loggers(json_logger: bool):
|
|
|
49
48
|
|
|
50
49
|
default_processors = get_default_processors(json_logger=json_logger)
|
|
51
50
|
|
|
51
|
+
if json_logger:
|
|
52
|
+
# don't use ORJSON here, as the stdlib formatter chain expects a str not a bytes
|
|
53
|
+
final_renderer = structlog.processors.JSONRenderer(sort_keys=True)
|
|
54
|
+
else:
|
|
55
|
+
# use the default renderer, which is the last processor
|
|
56
|
+
final_renderer = default_processors[-1]
|
|
57
|
+
|
|
52
58
|
formatter = ProcessorFormatter(
|
|
53
59
|
processors=[
|
|
54
60
|
# required to strip extra keys that the structlog stdlib bindings add in
|
|
55
61
|
structlog.stdlib.ProcessorFormatter.remove_processors_meta,
|
|
56
|
-
|
|
57
|
-
if not is_production() and not is_staging()
|
|
58
|
-
# don't use ORJSON here, as the stdlib formatter chain expects a str not a bytes
|
|
59
|
-
else structlog.processors.JSONRenderer(sort_keys=True),
|
|
62
|
+
final_renderer,
|
|
60
63
|
],
|
|
61
64
|
# processors unique to stdlib logging
|
|
62
65
|
foreign_pre_chain=[
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"""
|
|
2
|
+
Adds a TRACE log level to the standard logging module and structlog.
|
|
3
|
+
|
|
4
|
+
Some people believe that the standard log levels are not enough, and I'm with them.
|
|
5
|
+
|
|
2
6
|
Adapted from:
|
|
3
7
|
- https://github.com/willmcgugan/httpx/blob/973d1ed4e06577d928061092affe8f94def03331/httpx/_utils.py#L231
|
|
4
8
|
- https://github.com/vladmandic/sdnext/blob/d5d857aa961edbc46c9e77e7698f2e60011e7439/installer.py#L154
|
|
5
|
-
|
|
6
|
-
TODO this is not fully integrated into the codebase
|
|
7
9
|
"""
|
|
8
10
|
|
|
9
11
|
import logging
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
[project]
|
|
2
|
-
name = "structlog-config"
|
|
3
|
-
version = "0.5.0"
|
|
4
|
-
description = "A comprehensive structlog configuration with sensible defaults for development and production environments, featuring context management, exception formatting, and path prettification."
|
|
5
|
-
keywords = ["logging", "structlog", "json-logging", "structured-logging"]
|
|
6
|
-
readme = "README.md"
|
|
7
|
-
requires-python = ">=3.10"
|
|
8
|
-
dependencies = [
|
|
9
|
-
"orjson>=3.10.15",
|
|
10
|
-
"python-decouple-typed>=3.11.0",
|
|
11
|
-
"python-ipware>=3.0.0",
|
|
12
|
-
"structlog>=25.2.0",
|
|
13
|
-
]
|
|
14
|
-
authors = [{ name = "Michael Bianco", email = "mike@mikebian.co" }]
|
|
15
|
-
urls = { "Repository" = "https://github.com/iloveitaly/structlog-config" }
|
|
16
|
-
|
|
17
|
-
[build-system]
|
|
18
|
-
requires = ["uv_build>=0.8.11,<0.9.0"]
|
|
19
|
-
build-backend = "uv_build"
|
|
20
|
-
|
|
21
|
-
[tool.uv.build-backend]
|
|
22
|
-
# avoids the src/ directory structure
|
|
23
|
-
module-root = ""
|
|
24
|
-
|
|
25
|
-
[dependency-groups]
|
|
26
|
-
debugging-extras = [
|
|
27
|
-
"colorama>=0.4.6",
|
|
28
|
-
"datamodel-code-generator>=0.28.5",
|
|
29
|
-
"debugpy>=1.8.13",
|
|
30
|
-
"docrepr>=0.2.0",
|
|
31
|
-
"funcy-pipe>=0.11.1",
|
|
32
|
-
"httpdbg>=1.1.2",
|
|
33
|
-
"icecream>=2.1.4",
|
|
34
|
-
"ipdb",
|
|
35
|
-
"ipython>=8.34.0",
|
|
36
|
-
"ipython-autoimport>=0.5.1",
|
|
37
|
-
"ipython-ctrlr-fzf>=0.2.1",
|
|
38
|
-
"ipython-playground>=0.2.0",
|
|
39
|
-
"ipython-suggestions",
|
|
40
|
-
"ipythonclipboard>=1.0b2",
|
|
41
|
-
"jedi>=0.19.2",
|
|
42
|
-
"pdbr[ipython]>=0.9.0",
|
|
43
|
-
"pipdeptree>=2.26.0",
|
|
44
|
-
"pre-commit>=4.2.0",
|
|
45
|
-
"pretty-traceback",
|
|
46
|
-
"pudb>=2024.1.3",
|
|
47
|
-
"py-spy>=0.4.0",
|
|
48
|
-
"pyfzf>=0.3.1",
|
|
49
|
-
"pytest-fzf>=0.1.2.post1",
|
|
50
|
-
"rich>=14.0.0",
|
|
51
|
-
"rpdb>=0.2.0",
|
|
52
|
-
"sqlparse>=0.5.3",
|
|
53
|
-
"uv-development-toggle>=0.4.0",
|
|
54
|
-
]
|
|
55
|
-
dev = ["fastapi>=0.115.12", "httpx>=0.28.1", "pytest>=8.3.3"]
|
|
56
|
-
|
|
57
|
-
[tool.uv.sources]
|
|
58
|
-
ipdb = { git = "https://github.com/iloveitaly/ipdb", rev = "support-executables" }
|
|
59
|
-
pdbr = { git = "https://github.com/iloveitaly/pdbr", rev = "ipython-9.x" }
|
|
60
|
-
pretty-traceback = { git = "https://github.com/iloveitaly/pretty-traceback.git", rev = "custom" }
|
|
61
|
-
ipython-suggestions = { git = "https://github.com/iloveitaly/ipython-suggestions.git", rev = "ipython-9.x" }
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|