Skip to content

Commit fc6b6f2

Browse files
Create main endpoint and remove old endpoints (#78)
* refactor: Remove unused features * feat: create main get_configuration endpoint and add tests * chore: Improve CI Co-authored-by: Joseph Ware <[email protected]>
1 parent 4b0a2e2 commit fc6b6f2

File tree

23 files changed

+180
-475
lines changed

23 files changed

+180
-475
lines changed

.github/workflows/_test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ jobs:
5050
python-version: ${{ inputs.python-version }}
5151
pip-install: ".[dev,server]"
5252

53-
- name: Run tests
54-
run: tox -e tests
53+
- name: Run unit tests
54+
run: tox -e unit_tests
5555

5656
- name: Upload coverage to Codecov
5757
uses: codecov/codecov-action@v4

Dockerfile

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
2727
COPY --from=build /venv/ /venv/
2828
COPY tests/test_data/beamline_parameters.txt tests/test_data/beamline_parameters.txt
2929
ENV PATH=/venv/bin:$PATH
30-
ARG RUN_APP_IN_DEV_MODE=0
31-
ENV DEV_MODE=${RUN_APP_IN_DEV_MODE}
3230

3331
# change this entrypoint if it is not the same as the repo
3432
CMD daq-config-server

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ use_stub_offsets: bool = config_server.best_effort_get_feature_flag("use_stub_of
3535

3636
## Testing and deployment
3737

38+
3839
There is a convenient script in `./deployment/build_and_push.sh`, which takes
3940
a `--dev` option to push containers with `-dev` appended to their names and a `--no-push` option for local
4041
development. This ensures that environment variables for dev or prod builds are included in the built container. To push to

deployment/build_and_push.sh

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,8 @@ MAIN_CONTAINER_TAG="${BASE_REPO_ADDR}${MAIN_CONTAINER_NAME}"
4444
# set env vars which will be used by build process:
4545
if [ $DEV -gt 0 ]; then
4646
export REACT_APP_BACKEND_ADDR="http://localhost:8555"
47-
export MAIN_APP_DEV_MODE=1
4847
else
4948
export REACT_APP_BACKEND_ADDR="https://daq-config.diamond.ac.uk/api"
50-
export MAIN_APP_DEV_MODE=0
5149
fi
5250

5351
echo " "
@@ -57,7 +55,7 @@ echo "========================================="
5755
echo " "
5856
echo "Building ${MAIN_CONTAINER_NAME}"
5957
echo " "
60-
podman build --build-arg RUN_APP_IN_DEV_MODE=$MAIN_APP_DEV_MODE -t $MAIN_CONTAINER_NAME .
58+
podman build --build-arg -t $MAIN_CONTAINER_NAME .
6159
if [ $PUSH -gt 0 ]; then
6260
podman tag $MAIN_CONTAINER_NAME $MAIN_CONTAINER_TAG
6361
podman push $MAIN_CONTAINER_NAME $MAIN_CONTAINER_TAG

pyproject.toml

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,14 @@ write_to = "src/daq_config_server/_version.py"
5151
reportMissingImports = false # Ignore missing stubs in imported modules
5252

5353
[tool.pytest.ini_options]
54+
asyncio_mode = "auto"
55+
asyncio_default_fixture_loop_scope = "function"
5456
# Run pytest with all our checkers, and don't spam us with massive tracebacks on error
5557
addopts = """
5658
--tb=native -vv
57-
--asyncio-mode=auto
5859
"""
5960
markers = """
60-
uses_live_server: mark a test which uses the live config server
61+
requires_local_server: mark a test which requires locally hosting the config server
6162
"""
6263

6364
# https://iscinumpy.gitlab.io/post/bound-version-constraints/#watch-for-warnings
@@ -79,18 +80,22 @@ legacy_tox_ini = """
7980
[tox]
8081
skipsdist=True
8182
82-
[testenv:{pre-commit,type-checking,tests}]
83+
[testenv]
8384
# Don't create a virtualenv for the command, requires tox-direct plugin
8485
direct = True
8586
passenv = *
8687
allowlist_externals =
8788
pytest
8889
pre-commit
8990
pyright
91+
sphinx-build
92+
sphinx-autobuild
93+
[testenv:{pre-commit,type-checking,unit_tests,system_tests}]
9094
commands =
9195
pre-commit: pre-commit run --all-files --show-diff-on-failure {posargs}
9296
type-checking: pyright src tests {posargs}
93-
tests: pytest -m "not uses_live_server" --cov=daq_config_server --cov-report term --cov-report xml:cov.xml {posargs}
97+
unit_tests: pytest --cov=daq_config_server --cov-report term --cov-report xml:cov.xml {posargs} tests/unit_tests
98+
system_tests: pytest tests/system_tests
9499
"""
95100

96101
[tool.ruff]

src/daq_config_server/__main__.py

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,35 @@
22

33
from . import __version__
44

5-
try:
6-
import redis # noqa
7-
import uvicorn # noqa
8-
from fastapi import FastAPI # noqa
5+
__all__ = ["main"]
96

10-
server_dependencies_exist = True
11-
except ImportError:
12-
server_dependencies_exist = False
7+
INSUFFICIENT_DEPENDENCIES_MESSAGE = "To do anything other than print the version and be\
8+
available for importing the client, you must install this package with [server]\
9+
optional dependencies"
1310

1411

15-
__all__ = ["main"]
12+
def check_server_dependencies():
13+
try:
14+
import uvicorn # noqa
15+
from fastapi import FastAPI # noqa
16+
17+
return True
18+
19+
except ImportError:
20+
return False
1621

1722

1823
def main():
1924
parser = ArgumentParser()
2025
parser.add_argument("-v", "--version", action="version", version=__version__)
21-
parser.add_argument("-d", "--dev", action="store_true")
22-
args = parser.parse_args()
23-
if not server_dependencies_exist:
24-
print(
25-
"To do anything other than print the version and be available for "
26-
"importing the client, you must install this package with [server] "
27-
"optional dependencies"
28-
)
26+
parser.parse_args()
27+
28+
if not check_server_dependencies():
29+
print(INSUFFICIENT_DEPENDENCIES_MESSAGE)
2930
else:
3031
from .app import main
3132

32-
main(args)
33+
main()
3334

3435

3536
# test with: python -m daq_config_server

src/daq_config_server/app.py

Lines changed: 15 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,14 @@
1-
from os import environ
1+
from pathlib import Path
22

33
import uvicorn
4-
from fastapi import FastAPI, Request, Response, status
4+
from fastapi import FastAPI
55
from fastapi.middleware.cors import CORSMiddleware
6-
from pydantic import BaseModel
7-
from redis import Redis
86

9-
from .beamline_parameters import (
10-
BEAMLINE_PARAMETER_PATHS,
11-
GDABeamlineParameters,
12-
)
13-
from .constants import DATABASE_KEYS, ENDPOINTS
14-
15-
DEV_MODE = bool(int(environ.get("DEV_MODE") or 0))
16-
17-
ROOT_PATH = "/api"
18-
print(f"{DEV_MODE=}")
19-
print(f"{ROOT_PATH=}")
20-
if DEV_MODE:
21-
print("Running in dev mode! not setting root path!")
22-
ROOT_PATH = ""
7+
from daq_config_server.constants import ENDPOINTS
238

249
app = FastAPI(
2510
title="DAQ config server",
26-
description="""For storing and fetching beamline parameters, etc. which are needed
27-
by more than one applicatioon or service""",
28-
root_path=ROOT_PATH,
11+
description="""For reading files stored on /dls_sw from another container""",
2912
)
3013
origins = ["*"]
3114
app.add_middleware(
@@ -36,124 +19,21 @@
3619
allow_headers=["*"],
3720
)
3821

39-
valkey = Redis(host="localhost", port=6379, decode_responses=True)
40-
4122
__all__ = ["main"]
4223

43-
BEAMLINE_PARAM_PATH = ""
44-
BEAMLINE_PARAMS: GDABeamlineParameters | None = None
45-
46-
47-
@app.get(ENDPOINTS.BL_PARAM + "/{param}")
48-
def get_beamline_parameter(param: str):
49-
"""Get a single beamline parameter"""
50-
assert BEAMLINE_PARAMS is not None
51-
return {param: BEAMLINE_PARAMS.params.get(param)}
52-
53-
54-
class ParamList(BaseModel):
55-
param_list: list[str]
56-
57-
58-
@app.get(ENDPOINTS.BL_PARAM)
59-
def get_all_beamline_parameters(param_list_data: ParamList | None = None):
60-
"""Get a dict of all the current beamline parameters."""
61-
assert BEAMLINE_PARAMS is not None
62-
if param_list_data is None:
63-
return BEAMLINE_PARAMS.params
64-
return {k: BEAMLINE_PARAMS.params.get(k) for k in param_list_data.param_list}
65-
66-
67-
@app.get(ENDPOINTS.FEATURE)
68-
def get_feature_flag_list(get_values: bool = False):
69-
"""Get a list of all the current feature flags, or a dict of all the current values
70-
if get_values=true is passed"""
71-
flags = valkey.smembers(DATABASE_KEYS.FEATURE_SET)
72-
if not get_values:
73-
return flags
74-
else:
75-
return {flag: bool(int(valkey.get(flag))) for flag in flags} # type: ignore
76-
77-
78-
@app.get(ENDPOINTS.FEATURE + "/{flag_name}")
79-
def get_feature_flag(flag_name: str, response: Response):
80-
"""Get the value of a feature flag"""
81-
if not valkey.sismember(DATABASE_KEYS.FEATURE_SET, flag_name):
82-
response.status_code = status.HTTP_404_NOT_FOUND
83-
return {"message": f"Feature flag {flag_name} does not exist!"}
84-
else:
85-
ret = int(valkey.get(flag_name)) # type: ignore # We checked if it exists above
86-
return {flag_name: bool(ret)}
87-
88-
89-
@app.post(ENDPOINTS.FEATURE + "/{flag_name}", status_code=status.HTTP_201_CREATED)
90-
def create_feature_flag(flag_name: str, response: Response, value: bool = False):
91-
"""Sets a feature flag, creating it if it doesn't exist. Default to False."""
92-
if valkey.sismember(DATABASE_KEYS.FEATURE_SET, flag_name):
93-
response.status_code = status.HTTP_409_CONFLICT
94-
return {"message": f"Feature flag {flag_name} already exists!"}
95-
else:
96-
valkey.sadd(DATABASE_KEYS.FEATURE_SET, flag_name)
97-
return {"success": valkey.set(flag_name, int(value))}
98-
99-
100-
@app.put(ENDPOINTS.FEATURE + "/{flag_name}")
101-
def set_feature_flag(flag_name: str, value: bool, response: Response):
102-
"""Sets a feature flag, return an error if it doesn't exist."""
103-
if not valkey.sismember(DATABASE_KEYS.FEATURE_SET, flag_name):
104-
response.status_code = status.HTTP_404_NOT_FOUND
105-
return {"message": f"Feature flag {flag_name} does not exist!"}
106-
else:
107-
return {"success": valkey.set(flag_name, int(value))}
108-
109-
110-
@app.delete(ENDPOINTS.FEATURE + "/{flag_name}")
111-
def delete_feature_flag(flag_name: str, response: Response):
112-
"""Delete a feature flag."""
113-
if not valkey.sismember(DATABASE_KEYS.FEATURE_SET, flag_name):
114-
response.status_code = status.HTTP_404_NOT_FOUND
115-
return {"message": f"Feature flag {flag_name} does not exist!"}
116-
else:
117-
valkey.srem(DATABASE_KEYS.FEATURE_SET, flag_name)
118-
return {"success": not valkey.sismember(DATABASE_KEYS.FEATURE_SET, flag_name)}
119-
120-
121-
@app.get(ENDPOINTS.INFO)
122-
def get_info(request: Request):
123-
"""Get some generic information about the request, mostly for debugging"""
124-
return {
125-
"message": "Welcome to daq-config API.",
126-
"root_path": request.scope.get("root_path"),
127-
"request_headers": request.headers,
128-
}
129-
130-
131-
if DEV_MODE:
132-
133-
@app.api_route("/{full_path:path}")
134-
async def catch_all(request: Request, full_path: str):
135-
return {
136-
"message": "resource not found, supplying info for debug",
137-
"root_path": request.scope.get("root_path"),
138-
"path": full_path,
139-
"request_headers": repr(request.headers),
140-
}
141-
142-
143-
def _load_beamline_params():
144-
global BEAMLINE_PARAMS
145-
BEAMLINE_PARAMS = GDABeamlineParameters.from_file(BEAMLINE_PARAM_PATH)
14624

25+
@app.get(ENDPOINTS.CONFIG + "/{file_path:path}")
26+
def get_configuration(file_path: Path):
27+
"""Read a file and return its contents completely unformatted as a string. After
28+
https://github.com/DiamondLightSource/daq-config-server/issues/67, this endpoint
29+
will convert commonly read files to a dictionary format
30+
"""
31+
if not file_path.is_file():
32+
raise FileNotFoundError(f"File {file_path} cannot be found")
14733

148-
def _set_beamline_param_path(dev: bool = True):
149-
global BEAMLINE_PARAM_PATH
150-
if dev:
151-
BEAMLINE_PARAM_PATH = "tests/test_data/beamline_parameters.txt"
152-
else:
153-
BEAMLINE_PARAM_PATH = BEAMLINE_PARAMETER_PATHS["i03"]
34+
with file_path.open("r", encoding="utf-8") as f:
35+
return f.read()
15436

15537

156-
def main(args):
157-
_set_beamline_param_path(args.dev)
158-
_load_beamline_params()
38+
def main():
15939
uvicorn.run(app="daq_config_server.app:app", host="0.0.0.0", port=8555)

0 commit comments

Comments
 (0)