|
1 | | -""" |
2 | | -This is the file containing all of the endpoints for our flask app. |
3 | | -The endpoint called `endpoints` will return all available endpoints. |
4 | | -""" |
| 1 | +"""General-purpose API endpoints.""" |
5 | 2 |
|
| 3 | +import os |
6 | 4 | from http import HTTPStatus |
7 | 5 |
|
8 | 6 | from flask_restx import Resource, Namespace |
9 | 7 |
|
10 | 8 | import data.countries as countries_db |
11 | 9 | import data.states as states_db |
| 10 | +from data.db_connect import CLOUD, LOCAL, SE_DB |
| 11 | +from server.app import ( |
| 12 | + APP_NAME, |
| 13 | + get_cache_enabled, |
| 14 | + get_runtime_environment, |
| 15 | + get_runtime_log_level, |
| 16 | + get_runtime_port, |
| 17 | + get_runtime_version, |
| 18 | +) |
12 | 19 |
|
13 | 20 | # Create namespace for each resource |
14 | 21 | general_ns = Namespace("general", description="General API operations") |
15 | | -countries_ns = Namespace( |
16 | | - "countries", description="Operations related to countries" |
17 | | -) |
| 22 | +countries_ns = Namespace("countries", description="Operations related to countries") |
18 | 23 | states_ns = Namespace("states", description="Operations related to states") |
19 | 24 |
|
20 | 25 | # Constants for endpoints and responses |
21 | 26 | HELLO_EP = "/hello" |
22 | 27 | HELLO_RESP = "hello" |
23 | 28 |
|
24 | 29 |
|
| 30 | +def _parse_feature_flag(raw_value: str) -> bool | int | str: |
| 31 | + lowered = raw_value.lower() |
| 32 | + if lowered in {"true", "false"}: |
| 33 | + return lowered == "true" |
| 34 | + if raw_value.isdigit(): |
| 35 | + return int(raw_value) |
| 36 | + return raw_value |
| 37 | + |
| 38 | + |
| 39 | +def _get_feature_flags() -> dict[str, bool | int | str]: |
| 40 | + feature_flags = {} |
| 41 | + for key, value in os.environ.items(): |
| 42 | + if key.startswith("FEATURE_"): |
| 43 | + feature_name = key.removeprefix("FEATURE_").lower() |
| 44 | + feature_flags[feature_name] = _parse_feature_flag(value) |
| 45 | + return dict(sorted(feature_flags.items())) |
| 46 | + |
| 47 | + |
| 48 | +def _get_build_metadata() -> dict[str, str]: |
| 49 | + metadata_fields = { |
| 50 | + "commit_sha": os.getenv("GIT_SHA"), |
| 51 | + "build_id": os.getenv("BUILD_ID"), |
| 52 | + "release_id": os.getenv("RELEASE_ID"), |
| 53 | + "deploy_id": os.getenv("DEPLOY_ID"), |
| 54 | + } |
| 55 | + return {key: value for key, value in metadata_fields.items() if value} |
| 56 | + |
| 57 | + |
| 58 | +def _get_safe_database_config() -> dict[str, str | bool]: |
| 59 | + return { |
| 60 | + "name": os.getenv("DB_NAME", SE_DB), |
| 61 | + "mode": "cloud" if os.getenv("CLOUD_MONGO", LOCAL) == CLOUD else "local", |
| 62 | + } |
| 63 | + |
| 64 | + |
25 | 65 | @general_ns.route("/hello") |
26 | 66 | class HelloWorld(Resource): |
27 | 67 | """ |
@@ -76,6 +116,27 @@ def get(self): |
76 | 116 | The `get()` method will return a sorted list of available endpoints. |
77 | 117 | """ |
78 | 118 | from flask import current_app |
79 | | - endpoints = sorted(rule.rule for rule in |
80 | | - current_app.url_map.iter_rules()) |
| 119 | + |
| 120 | + endpoints = sorted(rule.rule for rule in current_app.url_map.iter_rules()) |
81 | 121 | return {"Available endpoints": endpoints} |
| 122 | + |
| 123 | + |
| 124 | +@general_ns.route("/dev/config") |
| 125 | +class DevConfig(Resource): |
| 126 | + """Return curated runtime configuration that is safe to expose.""" |
| 127 | + |
| 128 | + def get(self): |
| 129 | + payload = { |
| 130 | + "app_name": APP_NAME, |
| 131 | + "environment": get_runtime_environment(), |
| 132 | + "version": get_runtime_version(), |
| 133 | + "port": get_runtime_port(), |
| 134 | + "log_level": get_runtime_log_level(), |
| 135 | + "feature_flags": _get_feature_flags(), |
| 136 | + "database": _get_safe_database_config(), |
| 137 | + "cache_enabled": get_cache_enabled(), |
| 138 | + } |
| 139 | + build_metadata = _get_build_metadata() |
| 140 | + if build_metadata: |
| 141 | + payload["build"] = build_metadata |
| 142 | + return payload, HTTPStatus.OK |
0 commit comments