From 91d1bb54d339887058c75a98d086b595ca12cc3d Mon Sep 17 00:00:00 2001 From: Vadim Laletin Date: Fri, 15 Nov 2024 11:35:16 +0100 Subject: [PATCH] feat(cli, export): re-write using CLI and add export command --- .gitignore | 3 +- Dockerfile | 4 +- Dockerfile.resources | 2 +- README.md | 6 ++- fhirsnake/cli.py | 72 ++++++++++++++++++++++++++++++++ fhirsnake/initial_resources.py | 20 +++++++++ fhirsnake/{main.py => server.py} | 19 +-------- poetry.lock | 13 +++++- pyproject.toml | 1 + 9 files changed, 118 insertions(+), 22 deletions(-) create mode 100644 fhirsnake/cli.py create mode 100644 fhirsnake/initial_resources.py rename fhirsnake/{main.py => server.py} (80%) diff --git a/.gitignore b/.gitignore index b99e669..b512c00 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,9 @@ # IDE +.DS_Store .idea # Build dist # Versioning -node_modules \ No newline at end of file +node_modules diff --git a/Dockerfile b/Dockerfile index d4494e1..c889acd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,4 +12,6 @@ COPY fhirsnake /app EXPOSE 8000 -CMD ["poetry", "run", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] \ No newline at end of file +ENTRYPOINT ["poetry", "run", "python3", "cli.py"] + +CMD ["server"] diff --git a/Dockerfile.resources b/Dockerfile.resources index a6988a0..744cdef 100644 --- a/Dockerfile.resources +++ b/Dockerfile.resources @@ -1,4 +1,4 @@ FROM bedasoftware/fhirsnake:latest COPY resources /app/resources -# The destination should be exactly the same! \ No newline at end of file +# The destination should be exactly the same! diff --git a/README.md b/README.md index bdd5f6f..902eae1 100644 --- a/README.md +++ b/README.md @@ -36,13 +36,17 @@ resources/ 2. Adjust source destination in `Dockerfile.resources` if required 3. Option A: Run a container ```bash - docker run -p 8002:8000 -v ./resources:/app/resources bedasoftware/fhirsnake + docker run -p 8002:8000 -v ./resources:/app/resources bedasoftware/fhirsnake ``` 3. Option B: Build an image using the base image ```bash docker build -t fhirsnake-resources:latest -f Dockerfile.resources . docker run -p 8000:8000 fhirsnake-resources ``` +4. Option C: Export resources as .ndjson or ndjson.gz + ```bash + docker run -v ./resources:/app/resources -v ./output:/output bedasoftware/fhirsnake export --output /output/seeds.ndjson.gz + ``` ## Contribution and feedback Please, use [Issues](https://github.com/beda-software/fhirsnake/issues) diff --git a/fhirsnake/cli.py b/fhirsnake/cli.py new file mode 100644 index 0000000..c65dbd3 --- /dev/null +++ b/fhirsnake/cli.py @@ -0,0 +1,72 @@ +import argparse +import gzip +import logging + +import ndjson +import uvicorn + +from initial_resources import initial_resources + +logging.basicConfig(level=logging.INFO) + + +def main() -> None: + parser = argparse.ArgumentParser(description="CLI for fhirsnake") + + subparsers = parser.add_subparsers(dest="command", required=True, help="Sub-command to run") + + export_parser = subparsers.add_parser("export", help="Export resources as .ndjson or .ndjson.gz") + export_parser.add_argument( + "--output", + required=True, + help="Specify the output filename", + ) + + server_parser = subparsers.add_parser("server", help="Run fhirsnake FHIR server") + server_parser.add_argument( + "--host", + default="0.0.0.0", + help="Host", + ) + + server_parser.add_argument( + "--port", + type=int, + default=8000, + help="Port", + ) + + args = parser.parse_args() + + if args.command == "export": + export_resources(args.output) + + if args.command == "server": + server(args.host, args.port) + + +def server(host: str, port: int) -> None: + config = uvicorn.Config("server:app", host=host, port=port) + server = uvicorn.Server(config) + server.run() + + +def export_resources(output: str) -> None: + gzipped = output.endswith(".gz") + resources_list = flatten_resources(initial_resources) + dumped_resources = ndjson.dumps(resources_list) + + if gzipped: + with gzip.open(output, "w+") as f: + f.write(dumped_resources.encode()) + else: + with open(output, "w+") as f: + f.write(dumped_resources) + + +def flatten_resources(resources: dict[str, dict[str, dict]]) -> list[dict]: + return [resource for by_resource_type in resources.values() for resource in by_resource_type.values()] + + +if __name__ == "__main__": + main() diff --git a/fhirsnake/initial_resources.py b/fhirsnake/initial_resources.py new file mode 100644 index 0000000..8c125c6 --- /dev/null +++ b/fhirsnake/initial_resources.py @@ -0,0 +1,20 @@ +import logging +import os +import sys + +from files import load_resources + +REPOSITORY_URL = "https://github.com/beda-software/fhirsnake/" +RESOURCE_DIR = "resources" + +root_dir = os.path.dirname(os.path.abspath(__name__)) +resources_abs_path = os.path.join(root_dir, RESOURCE_DIR) + +if not os.path.isdir(resources_abs_path): + logging.error( + f"Required directory '{resources_abs_path}' does not exist. \ + Check {REPOSITORY_URL} for details. Stopping application." + ) + sys.exit(1) + +initial_resources = load_resources(resources_abs_path) diff --git a/fhirsnake/main.py b/fhirsnake/server.py similarity index 80% rename from fhirsnake/main.py rename to fhirsnake/server.py index 44daf69..b12a29b 100644 --- a/fhirsnake/main.py +++ b/fhirsnake/server.py @@ -1,33 +1,18 @@ import logging -import os -import sys import uuid from fastapi import FastAPI, HTTPException -from files import load_resources +from initial_resources import initial_resources logging.basicConfig(level=logging.INFO) -REPOSITORY_URL = "https://github.com/beda-software/fhirsnake/" app = FastAPI() @app.on_event("startup") async def load_app_data(): - root_dir = os.path.dirname(os.path.abspath(__name__)) - logging.warning(root_dir) - resource_dir = "resources" - resources_abs_path = os.path.join(root_dir, resource_dir) - - if not os.path.isdir(resources_abs_path): - logging.error( - f"Required directory '{resources_abs_path}' does not exist. \ - Check {REPOSITORY_URL} for details. Stopping application." - ) - sys.exit(1) - - app.state.resources = load_resources(resources_abs_path) + app.state.resources = initial_resources @app.get("/") diff --git a/poetry.lock b/poetry.lock index eec3c28..4a9b6dc 100644 --- a/poetry.lock +++ b/poetry.lock @@ -98,6 +98,17 @@ files = [ {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] +[[package]] +name = "ndjson" +version = "0.3.1" +description = "JsonDecoder for ndjson" +optional = false +python-versions = "*" +files = [ + {file = "ndjson-0.3.1-py2.py3-none-any.whl", hash = "sha256:839c22275e6baa3040077b83c005ac24199b94973309a8a1809be962c753a410"}, + {file = "ndjson-0.3.1.tar.gz", hash = "sha256:bf9746cb6bb1cb53d172cda7f154c07c786d665ff28341e4e689b796b229e5d6"}, +] + [[package]] name = "pydantic" version = "2.8.2" @@ -370,4 +381,4 @@ standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "9afb4802e49981031786178bf604bf07ef5e4fc1efdc5d9437849a99cd1c4623" +content-hash = "fc6650e8059dd9dd77e4fb7c6b35e91f529beb6b942852c643e1e976f1e00f72" diff --git a/pyproject.toml b/pyproject.toml index b5a44dc..fd560ed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,6 +11,7 @@ python = "^3.11" fastapi = "^0.112.1" uvicorn = "^0.30.6" pyyaml = "^6.0.2" +ndjson = "^0.3.1" [tool.poetry.dev-dependencies] ruff = "^0.6.2"