Skip to content

Commit 371e2d7

Browse files
Merge pull request #98 from labthings/more-fallback-info
Add more information to the fallback server
2 parents 721b352 + b232bc1 commit 371e2d7

File tree

6 files changed

+123
-8
lines changed

6 files changed

+123
-8
lines changed

.coveragerc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[run]
2+
concurrency = multiprocessing, thread
3+
parallel = true
4+
sigterm = true
5+
omit = tests/**/*.py, docs/**/*.py

.github/workflows/test.yml

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ on:
55

66
jobs:
77
base_coverage:
8+
continue-on-error: true
89
runs-on: ubuntu-latest
910
steps:
1011
- uses: actions/checkout@v3
@@ -17,7 +18,7 @@ jobs:
1718
python-version: 3.12
1819

1920
- name: Install Dependencies
20-
run: pip install -e .[dev,server]
21+
run: pip install -e . -r dev-requirements.txt
2122

2223
- name: Test with pytest
2324
run: |
@@ -111,11 +112,16 @@ jobs:
111112
name: coverage-3.12
112113

113114
- name: Download code coverage report for base branch
115+
id: download-base-coverage
116+
continue-on-error: true
114117
uses: actions/download-artifact@v4
115118
with:
116119
name: base-coverage.lcov
117120

118121
- name: Generate Code Coverage report
122+
# Note, due to continue on error (to make job pass) we need to check the
123+
# Status of the step directly not just use success() or failure()
124+
if: steps.download-base-coverage.outcome == 'success'
119125
id: code-coverage
120126
uses: barecheck/code-coverage-action@v1
121127
with:
@@ -124,4 +130,15 @@ jobs:
124130
base-lcov-file: "./base-coverage.lcov"
125131
minimum-ratio: 0
126132
send-summary-comment: true
127-
show-annotations: "warning"
133+
show-annotations: "warning"
134+
135+
- name: Generate Code Coverage report if base job fails
136+
if: steps.download-base-coverage.outcome == 'failure'
137+
id: code-coverage-without-base
138+
uses: barecheck/code-coverage-action@v1
139+
with:
140+
barecheck-github-app-token: ${{ secrets.BARECHECK_GITHUB_APP_TOKEN }}
141+
lcov-file: "./coverage.lcov"
142+
minimum-ratio: 0
143+
send-summary-comment: true
144+
show-annotations: "warning"

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "labthings-fastapi"
3-
version = "0.0.7"
3+
version = "0.0.8"
44
authors = [
55
{ name="Richard Bowman", email="richard.bowman@cantab.net" },
66
]
@@ -13,7 +13,7 @@ classifiers = [
1313
"Operating System :: OS Independent",
1414
]
1515
dependencies = [
16-
"pydantic>=2.0.0",
16+
"pydantic ~= 2.10.6",
1717
"numpy>=1.20",
1818
"jsonschema",
1919
"typing_extensions",

src/labthings_fastapi/server/fallback.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import json
2+
from traceback import format_exception
23
from fastapi import FastAPI
34
from fastapi.responses import HTMLResponse
45
from starlette.responses import RedirectResponse
@@ -10,6 +11,7 @@ def __init__(self, *args, **kwargs):
1011
self.labthings_config = None
1112
self.labthings_server = None
1213
self.labthings_error = None
14+
self.log_history = None
1315

1416

1517
app = FallbackApp()
@@ -32,6 +34,9 @@ def __init__(self, *args, **kwargs):
3234
</ul>
3335
<p>Your configuration:</p>
3436
<pre>{{config}}</pre>
37+
<p>Traceback</p>
38+
<pre>{{traceback}}</pre>
39+
{{logginginfo}}
3540
</body>
3641
</html>
3742
"""
@@ -40,6 +45,9 @@ def __init__(self, *args, **kwargs):
4045
@app.get("/")
4146
async def root():
4247
error_message = f"{app.labthings_error!r}"
48+
# use traceback.format_exception to get full traceback as list
49+
# this ends in newlines, but needs joining to be a single string
50+
error_w_trace = "".join(format_exception(app.labthings_error))
4351
things = ""
4452
if app.labthings_server:
4553
for path, thing in app.labthings_server.things.items():
@@ -49,6 +57,14 @@ async def root():
4957
content = content.replace("{{error}}", error_message)
5058
content = content.replace("{{things}}", things)
5159
content = content.replace("{{config}}", json.dumps(app.labthings_config, indent=2))
60+
content = content.replace("{{traceback}}", error_w_trace)
61+
62+
if app.log_history is None:
63+
logging_info = " <p>No logging info available</p>"
64+
else:
65+
logging_info = f" <p>Logging info</p>\n <pre>{app.log_history}</pre>"
66+
67+
content = content.replace("{{logginginfo}}", logging_info)
5268
return HTMLResponse(content=content, status_code=500)
5369

5470

tests/test_fallback.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
from fastapi.testclient import TestClient
2+
from labthings_fastapi.server import server_from_config
3+
from labthings_fastapi.server.fallback import app
4+
5+
6+
def test_fallback_empty():
7+
with TestClient(app) as client:
8+
response = client.get("/")
9+
html = response.text
10+
# test that something when wrong is shown
11+
assert "Something went wrong" in html
12+
assert "No logging info available" in html
13+
14+
15+
def test_fallback_with_config():
16+
app.labthings_config = {"hello": "goodbye"}
17+
with TestClient(app) as client:
18+
response = client.get("/")
19+
html = response.text
20+
assert "Something went wrong" in html
21+
assert "No logging info available" in html
22+
assert '"hello": "goodbye"' in html
23+
24+
25+
def test_fallback_with_error():
26+
app.labthings_error = RuntimeError("Custom error message")
27+
with TestClient(app) as client:
28+
response = client.get("/")
29+
html = response.text
30+
assert "Something went wrong" in html
31+
assert "No logging info available" in html
32+
assert "RuntimeError" in html
33+
assert "Custom error message" in html
34+
35+
36+
def test_fallback_with_server():
37+
config = {
38+
"things": {
39+
"thing1": "labthings_fastapi.example_things:MyThing",
40+
"thing2": {
41+
"class": "labthings_fastapi.example_things:MyThing",
42+
"kwargs": {},
43+
},
44+
}
45+
}
46+
app.labthings_server = server_from_config(config)
47+
with TestClient(app) as client:
48+
response = client.get("/")
49+
html = response.text
50+
assert "Something went wrong" in html
51+
assert "No logging info available" in html
52+
assert "thing1/" in html
53+
assert "thing2/" in html
54+
55+
56+
def test_fallback_with_log():
57+
app.log_history = "Fake log conetent"
58+
with TestClient(app) as client:
59+
response = client.get("/")
60+
html = response.text
61+
assert "Something went wrong" in html
62+
assert "No logging info available" not in html
63+
assert "<p>Logging info</p>" in html
64+
assert "Fake log conetent" in html

tests/test_server_cli.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,8 @@ def test_serve_with_no_config():
119119
check_serve_from_cli([])
120120

121121

122-
def test_invalid_thing_and_fallback():
123-
"""Check it fails for invalid things, and test the fallback option"""
122+
def test_invalid_thing():
123+
"""Check it fails for invalid things"""
124124
config_json = json.dumps(
125125
{
126126
"things": {
@@ -130,8 +130,21 @@ def test_invalid_thing_and_fallback():
130130
)
131131
with raises(ImportError):
132132
check_serve_from_cli(["-j", config_json])
133-
## the line below should start a dummy server with an error page -
134-
## it terminates happily once the server starts.
133+
134+
135+
def test_fallback():
136+
"""test the fallback option
137+
138+
startd a dummy server with an error page -
139+
it terminates once the server starts.
140+
"""
141+
config_json = json.dumps(
142+
{
143+
"things": {
144+
"broken": "labthings_fastapi.example_things:MissingThing",
145+
}
146+
}
147+
)
135148
check_serve_from_cli(["-j", config_json, "--fallback"])
136149

137150

0 commit comments

Comments
 (0)