Skip to content

Add more information to the fallback server #98

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Apr 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[run]
concurrency = multiprocessing, thread
parallel = true
sigterm = true
omit = tests/**/*.py, docs/**/*.py
21 changes: 19 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:

jobs:
base_coverage:
continue-on-error: true
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
Expand All @@ -17,7 +18,7 @@ jobs:
python-version: 3.12

- name: Install Dependencies
run: pip install -e .[dev,server]
run: pip install -e . -r dev-requirements.txt

- name: Test with pytest
run: |
Expand Down Expand Up @@ -111,11 +112,16 @@ jobs:
name: coverage-3.12

- name: Download code coverage report for base branch
id: download-base-coverage
continue-on-error: true
uses: actions/download-artifact@v4
with:
name: base-coverage.lcov

- name: Generate Code Coverage report
# Note, due to continue on error (to make job pass) we need to check the
# Status of the step directly not just use success() or failure()
if: steps.download-base-coverage.outcome == 'success'
id: code-coverage
uses: barecheck/code-coverage-action@v1
with:
Expand All @@ -124,4 +130,15 @@ jobs:
base-lcov-file: "./base-coverage.lcov"
minimum-ratio: 0
send-summary-comment: true
show-annotations: "warning"
show-annotations: "warning"

- name: Generate Code Coverage report if base job fails
if: steps.download-base-coverage.outcome == 'failure'
id: code-coverage-without-base
uses: barecheck/code-coverage-action@v1
with:
barecheck-github-app-token: ${{ secrets.BARECHECK_GITHUB_APP_TOKEN }}
lcov-file: "./coverage.lcov"
minimum-ratio: 0
send-summary-comment: true
show-annotations: "warning"
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "labthings-fastapi"
version = "0.0.7"
version = "0.0.8"
authors = [
{ name="Richard Bowman", email="[email protected]" },
]
Expand All @@ -13,7 +13,7 @@ classifiers = [
"Operating System :: OS Independent",
]
dependencies = [
"pydantic>=2.0.0",
"pydantic ~= 2.10.6",
"numpy>=1.20",
"jsonschema",
"typing_extensions",
Expand Down
16 changes: 16 additions & 0 deletions src/labthings_fastapi/server/fallback.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
from traceback import format_exception
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
from starlette.responses import RedirectResponse
Expand All @@ -10,6 +11,7 @@
self.labthings_config = None
self.labthings_server = None
self.labthings_error = None
self.log_history = None


app = FallbackApp()
Expand All @@ -32,6 +34,9 @@
</ul>
<p>Your configuration:</p>
<pre>{{config}}</pre>
<p>Traceback</p>
<pre>{{traceback}}</pre>
{{logginginfo}}
</body>
</html>
"""
Expand All @@ -40,6 +45,9 @@
@app.get("/")
async def root():
error_message = f"{app.labthings_error!r}"
# use traceback.format_exception to get full traceback as list
# this ends in newlines, but needs joining to be a single string
error_w_trace = "".join(format_exception(app.labthings_error))
things = ""
if app.labthings_server:
for path, thing in app.labthings_server.things.items():
Expand All @@ -49,9 +57,17 @@
content = content.replace("{{error}}", error_message)
content = content.replace("{{things}}", things)
content = content.replace("{{config}}", json.dumps(app.labthings_config, indent=2))
content = content.replace("{{traceback}}", error_w_trace)

if app.log_history is None:
logging_info = " <p>No logging info available</p>"
else:
logging_info = f" <p>Logging info</p>\n <pre>{app.log_history}</pre>"

content = content.replace("{{logginginfo}}", logging_info)
return HTMLResponse(content=content, status_code=500)


@app.get("/{path:path}")
async def redirect_to_root(path: str):
return RedirectResponse(url="/")

Check warning on line 73 in src/labthings_fastapi/server/fallback.py

View workflow job for this annotation

GitHub Actions / coverage

73 line is not covered with tests
64 changes: 64 additions & 0 deletions tests/test_fallback.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from fastapi.testclient import TestClient
from labthings_fastapi.server import server_from_config
from labthings_fastapi.server.fallback import app


def test_fallback_empty():
with TestClient(app) as client:
response = client.get("/")
html = response.text
# test that something when wrong is shown
assert "Something went wrong" in html
assert "No logging info available" in html


def test_fallback_with_config():
app.labthings_config = {"hello": "goodbye"}
with TestClient(app) as client:
response = client.get("/")
html = response.text
assert "Something went wrong" in html
assert "No logging info available" in html
assert '"hello": "goodbye"' in html


def test_fallback_with_error():
app.labthings_error = RuntimeError("Custom error message")
with TestClient(app) as client:
response = client.get("/")
html = response.text
assert "Something went wrong" in html
assert "No logging info available" in html
assert "RuntimeError" in html
assert "Custom error message" in html


def test_fallback_with_server():
config = {
"things": {
"thing1": "labthings_fastapi.example_things:MyThing",
"thing2": {
"class": "labthings_fastapi.example_things:MyThing",
"kwargs": {},
},
}
}
app.labthings_server = server_from_config(config)
with TestClient(app) as client:
response = client.get("/")
html = response.text
assert "Something went wrong" in html
assert "No logging info available" in html
assert "thing1/" in html
assert "thing2/" in html


def test_fallback_with_log():
app.log_history = "Fake log conetent"
with TestClient(app) as client:
response = client.get("/")
html = response.text
assert "Something went wrong" in html
assert "No logging info available" not in html
assert "<p>Logging info</p>" in html
assert "Fake log conetent" in html
21 changes: 17 additions & 4 deletions tests/test_server_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,8 @@ def test_serve_with_no_config():
check_serve_from_cli([])


def test_invalid_thing_and_fallback():
"""Check it fails for invalid things, and test the fallback option"""
def test_invalid_thing():
"""Check it fails for invalid things"""
config_json = json.dumps(
{
"things": {
Expand All @@ -130,8 +130,21 @@ def test_invalid_thing_and_fallback():
)
with raises(ImportError):
check_serve_from_cli(["-j", config_json])
## the line below should start a dummy server with an error page -
## it terminates happily once the server starts.


def test_fallback():
"""test the fallback option

startd a dummy server with an error page -
it terminates once the server starts.
"""
config_json = json.dumps(
{
"things": {
"broken": "labthings_fastapi.example_things:MissingThing",
}
}
)
check_serve_from_cli(["-j", config_json, "--fallback"])


Expand Down