Skip to content

Commit d3d3230

Browse files
committed
fix(config): better handle config and common library setup, remove deptry (it cannot handle this setup)
1 parent dc0b1f3 commit d3d3230

File tree

16 files changed

+2395
-765
lines changed

16 files changed

+2395
-765
lines changed

.gitignore

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ sbom_*.json
213213
*_requirements.txt
214214
pylock.*
215215
snyk
216+
.vscode
216217

217218
# We do not want library lock files b/c they're not
218219
# actually used by any applications (and they will)
@@ -226,3 +227,14 @@ snyk
226227
# common dep, but the actual installed version for the service
227228
# is reflected in the service's own uv.lock, which IS checked in.
228229
libraries/*/uv.lock
230+
231+
# gen3_inference
232+
knowledge/
233+
*.sqlite3
234+
chroma/
235+
cache/
236+
tests/prof/
237+
prof/
238+
bin/library
239+
tmp/
240+
*.bak

README.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,20 +41,23 @@ See [docs/ai_api.yaml](docs/ai_api.yaml) for the OpenAPI specification. You can
4141
* Simplified setup, building, running
4242
* `just install`, `just run gen3_embeddings`, `just build`
4343

44-
> Alternative: use a base service instead of library. Justification for library: explicitly labeling the common code as a library (e.g. not meant to be run by itself) provides more clarity on intended use.
45-
46-
Services can import common code from libraries:
44+
Services can import common code:
4745

4846
```python
49-
from libraries.common import TEST
47+
from common.config import DEBUG
5048
```
5149

52-
Services have folder structure:
50+
Services (and libraries) have folder structure:
5351

5452
* `src/{{name}}`
5553
* `pyproject.toml` which builds {{name}} from src/{{name}}
5654

57-
> Alternative: all services could share a common `gen3` (or similar) package. Justification to not sharing a common package: the `src` setup is a more explicit separation of concerns and discourages cross-service importing without using the common library.
55+
### Why this setup?
56+
57+
- General benefits of a monorepo (common patterns for maintaining code in a single repo)
58+
- Per-service uv environments ensure minimal required dependencies for each
59+
- Common library project allows cross-service code and dependencies to be
60+
maintained in one place
5861

5962
## Quickstart
6063

justfile

Lines changed: 35 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ setup:
1616
curl -LsSf https://astral.sh/uv/install.sh | sh
1717
fi
1818

19-
print_header "just setup:" "verifying" "postgres" "installation..."
19+
print_header "just setup:" "verifying" "PostgreSQL client (psql)" "installation..."
2020
if command -v psql >/dev/null 2>&1; then
2121
echo "PostgreSQL client (psql) is installed."
2222
echo " version: $(psql --version)"
@@ -28,28 +28,32 @@ setup:
2828

2929
for dir in services/*; do
3030
if [[ -n "${dir#services/}" ]]; then
31-
print_header "just setup:" "setting up postgres db for" "${dir#services/}" "service..."
32-
33-
if [ ! -f "${dir}/.env" ]; then
34-
echo "${RED}** WARNING: .env file not found in "${dir}". Will rely on environment variables. **${RESET}"
31+
if [ "${dir#services/}" == "gen3_inference" ]; then
32+
print_header "just setup:" "No PostgreSQL db needed for" "${dir#services/}" "service. Nothing to do."
3533
else
36-
echo "Found .env file. Using it to set up database."
37-
set -a
38-
source "${dir}/.env"
39-
set +a
40-
fi
41-
42-
if [[ -z ${PGDATABASE:-} ]]; then
43-
echo "PGDATABASE not set, using ${dir#services/}..."
44-
export PGDATABASE="${dir#services/}"
34+
print_header "just setup:" "setting up PostgreSQL db for" "${dir#services/}" "service..."
35+
36+
if [ ! -f "${dir}/.env" ]; then
37+
echo "${RED}** WARNING: .env file not found in "${dir}". Will rely on environment variables. **${RESET}"
38+
else
39+
echo "Found .env file. Using it to set up database."
40+
set -a
41+
source "${dir}/.env"
42+
set +a
43+
fi
44+
45+
if [[ -z ${PGDATABASE:-} ]]; then
46+
echo "PGDATABASE not set, using ${dir#services/}..."
47+
export PGDATABASE="${dir#services/}"
48+
fi
49+
50+
psql \
51+
-h "${PGHOST}" -p "${PGPORT}" -U "${PGUSER}" \
52+
-c "CREATE DATABASE \"${PGDATABASE}\" WITH OWNER \"${PGUSER}\";" \
53+
2>/dev/null || echo "Database already exists."
54+
55+
# TODO: db migration / initial setup
4556
fi
46-
47-
psql \
48-
-h "${PGHOST}" -p "${PGPORT}" -U "${PGUSER}" \
49-
-c "CREATE DATABASE \"${PGDATABASE}\" WITH OWNER \"${PGUSER}\";" \
50-
2>/dev/null || echo "Database already exists."
51-
52-
# TODO: db migration / initial setup
5357
fi
5458
done
5559

@@ -68,7 +72,14 @@ install $SERVICE="all":
6872
# print_header COMMAND TEXT SERVICE TEXT
6973
print_header "just install:" "installing" "$SERVICE" "service..."
7074

71-
uv sync --all-packages --group dev --directory "./services/$SERVICE"
75+
echo "Installing common library into service: ${SERVICE}..."
76+
cd "./services/$SERVICE"
77+
uv add "common @ file://../../libraries/common"
78+
cd -
79+
80+
echo
81+
echo "uv sync-ing $SERVICE service with --group dev and --all-extras..."
82+
uv sync --all-packages --group dev --directory "./services/$SERVICE" --all-extras
7283
fi
7384

7485
lock $SERVICE="all":
@@ -94,7 +105,7 @@ test $SERVICE="all":
94105
else
95106
print_header "just test:" "testing" "$SERVICE" "service..."
96107
cd "./services/$SERVICE"
97-
uv run pytest .
108+
uv run pytest . -vv
98109
exit_code=$?
99110
cd -
100111

@@ -129,7 +140,7 @@ build $SERVICE="all":
129140
export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=1
130141

131142
# Start the app with OpenTelemetry and Gunicorn and Uvicorn workers
132-
uv run --directory "./services/$SERVICE" \
143+
uv run --env-file "../../.env" --directory "./services/$SERVICE" \
133144
opentelemetry-instrument \
134145
gunicorn \
135146
$SERVICE.main:app_instance \
@@ -236,14 +247,6 @@ lint $SERVICE="all":
236247
overall_exit=$((overall_exit | $exit_code))
237248
echo
238249

239-
print_header "just lint:" "deptry" "$SERVICE" "..."
240-
uv run --directory "./services/$SERVICE" deptry ./src
241-
242-
exit_code=$?
243-
report_error_if_failed $exit_code "just lint:" "deptry" "$SERVICE" "service!"
244-
overall_exit=$((overall_exit | $exit_code))
245-
echo
246-
247250
report_error_or_success $overall_exit "just lint:" "linting" "$SERVICE" "service!"
248251

249252
exit $overall_exit

libraries/common/pyproject.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ dependencies = [
1717
"opentelemetry-sdk>=1.39.1",
1818
"prometheus-client>=0.24.1",
1919
"pytest>=9.0.2",
20+
"pyyaml>=6.0.3",
21+
"requests>=2.32.5",
22+
"ruff>=0.15.6",
2023
"starlette>=0.52.1",
2124
"uvicorn>=0.41.0",
2225
]
@@ -29,7 +32,6 @@ dev = [
2932
"pytest-xdist",
3033
"pyright",
3134
"pylint>=4.0.5",
32-
"deptry>=0.24.0",
3335
]
3436

3537
[tool.uv]
@@ -40,4 +42,4 @@ requires = ["hatchling"]
4042
build-backend = "hatchling.build"
4143

4244
[tool.hatch.build.targets.wheel]
43-
packages = ["common"]
45+
packages = ["src/common"]
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import os
2+
import sys
3+
from pathlib import Path
4+
5+
import cdislogging
6+
from starlette.config import Config
7+
8+
def get_venv_root() -> Path | None:
9+
"""
10+
Return the absolute Path to the root of the current virtual environment,
11+
or None if the interpreter is running from the system Python.
12+
"""
13+
if hasattr(sys, 'base_prefix'):
14+
if sys.prefix != sys.base_prefix:
15+
return Path(sys.prefix).parent
16+
17+
return None
18+
19+
# NOTE: Default config only works when:
20+
# The .env is in its standard location:
21+
# /services/{service_name}/.env
22+
# AND the common library is installed in a virtualenv for the service
23+
# AND the virtualenv directory is in:
24+
# /services/{service_name}/{venv_name}
25+
CURRENT_DIR = get_venv_root() or os.path.dirname(os.path.realpath(__file__))
26+
CONFIG_PATH = os.path.abspath(os.getenv("CONFIG_PATH", f"{CURRENT_DIR}/.env"))
27+
28+
starlette_config = Config(CONFIG_PATH)
29+
DEBUG = starlette_config("DEBUG", cast=bool, default=False)
30+
31+
# this turns on debug logging for certain noisy internal libraries
32+
# Note: the list of libraries is in the gunicorn.conf.py
33+
VERBOSE_INTERNAL_LOGS = starlette_config("VERBOSE_INTERNAL_LOGS", cast=bool, default=False)
34+
35+
logging = cdislogging.get_logger(__name__, log_level="debug" if DEBUG else "info")
36+
37+
logging.info(f"Using configuration file: {CONFIG_PATH}")
38+
39+
# will skip authorization when a token is not provided. note that if a token is provided, then
40+
# auth will still occur
41+
DEBUG_SKIP_AUTH = starlette_config("DEBUG_SKIP_AUTH", cast=bool, default=False)
42+
43+
# this will effectively turn off authorization checking,
44+
# allowing for anyone to use the AI functionality
45+
ALLOW_ANONYMOUS_ACCESS = starlette_config("ALLOW_ANONYMOUS_ACCESS", cast=bool, default=False)
46+
47+
logging.info(f"DEBUG is {DEBUG}")
48+
logging.info(f"VERBOSE_INTERNAL_LOGS is {VERBOSE_INTERNAL_LOGS}")
49+
50+
if DEBUG_SKIP_AUTH:
51+
logging.warning(
52+
f"DEBUG_SKIP_AUTH is {DEBUG_SKIP_AUTH}. Authorization will be SKIPPED if no token is provided. "
53+
"FOR NON-PRODUCTION USE ONLY!! USE WITH CAUTION!!"
54+
)
55+
if ALLOW_ANONYMOUS_ACCESS:
56+
logging.warning(
57+
f"ALLOW_ANONYMOUS_ACCESS is {ALLOW_ANONYMOUS_ACCESS}. Authorization will be SKIPPED. "
58+
"ENSURE THIS IS ACCEPTABLE!!"
59+
)
60+
61+
# Location of the policy engine service, Arborist
62+
# Defaults to the default service name in k8s magic DNS setup
63+
ARBORIST_URL = starlette_config("ARBORIST_URL", default="http://arborist-service", cast=str)
64+
65+
PUBLIC_ROUTES = {"/", "/_status", "/_status/", "/_version", "/_version/"}
66+
ENDPOINTS_WITHOUT_METRICS = {"/metrics", "/metrics/"} | PUBLIC_ROUTES
67+
68+
# This app exports traces using OpenTelemetry. By default in Gen3, we use Alloy for collection.
69+
ENABLE_OPENTELEMETRY_TRACES = starlette_config("ENABLE_OPENTELEMETRY_TRACES", cast=bool, default=True)
70+
# For local development, set this to an EMPTY STRING and it will output to console. See gunicorn.conf.py
71+
OTEL_EXPORTER_OTLP_ENDPOINT = starlette_config("OTEL_EXPORTER_OTLP_ENDPOINT", default="http://alloy.monitoring.4318", cast=str)
72+
73+
ASYNC_HTTP_CLIENT_TIMEOUT = starlette_config("ASYNC_HTTP_CLIENT_TIMEOUT", cast=float, default=30)
File renamed without changes.

services/gen3_ai_model_repo/pyproject.toml

Lines changed: 7 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
name = "gen3_ai_model_repo"
33
version = "0.1.0"
44
description = "Gen3 AI Model Repo"
5-
dependencies = ["common", "asyncpg"]
5+
dependencies = [
6+
"common",
7+
"asyncpg",
8+
]
69
requires-python = "~=3.13.0"
710

811
[dependency-groups]
@@ -11,42 +14,12 @@ dev = []
1114
[tool.uv]
1215
package = true
1316

14-
[tool.uv.workspace]
15-
members = [
16-
"../../libraries/*",
17-
]
18-
19-
[tool.uv.sources]
20-
common = { workspace = true }
21-
2217
[build-system]
2318
requires = ["hatchling"]
2419
build-backend = "hatchling.build"
2520

21+
[tool.uv.sources]
22+
common = { path = "../../libraries/common" }
23+
2624
[tool.hatch.build.targets.wheel]
2725
packages = ["src/gen3_ai_model_repo"]
28-
29-
[tool.deptry.per_rule_ignores]
30-
DEP002 = ["common"]
31-
32-
# anything in libraries/common we need to add here
33-
# b/c deptry will see it as a transitive dependency but
34-
# we actually WANT it to NOT be in both the service and
35-
# libraries/common. So this is intentional / by design
36-
# for shared libraries.
37-
DEP003 = [
38-
"authutils",
39-
"asyncpg",
40-
"cdislogging",
41-
"click",
42-
"fastapi",
43-
"gen3authz",
44-
"gunicorn",
45-
"opentelemetry-api",
46-
"opentelemetry-exporter-otlp",
47-
"opentelemetry-instrumentation-fastapi",
48-
"opentelemetry-sdk",
49-
"prometheus-client",
50-
"starlette",
51-
"uvicorn",
52-
]

0 commit comments

Comments
 (0)