Skip to content

Commit fe4e4c6

Browse files
committed
initial cli plus some cleanup
1 parent 66eb2b6 commit fe4e4c6

28 files changed

+407
-293
lines changed

MANIFEST.in

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
include guardrails_api/*
2+
exclude */__pycache__/*

Makefile

+14-13
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
11
# Installs production dependencies
22
install:
33
pip install -r requirements.txt;
4-
# This is a workaround because of this issue: https://github.com/open-telemetry/opentelemetry-python-contrib/issues/2053
5-
pip uninstall aiohttp -y
6-
pip install opentelemetry-distro
7-
opentelemetry-bootstrap -a install
8-
pip install aiohttp
94

105
# Installs development dependencies
116
install-dev:
@@ -18,8 +13,13 @@ lock:
1813
install-lock:
1914
pip install -r requirements-lock.txt
2015

16+
.PHONY: build
17+
build:
18+
curl https://raw.githubusercontent.com/guardrails-ai/guardrails-api-client/main/service-specs/guardrails-service-spec.yml -o ./open-api-spec.yml
19+
npx @redocly/cli bundle --dereferenced --output ./guardrails_api/open-api-spec.json --ext json ./open-api-spec.yml
20+
2121
start:
22-
bash start.sh
22+
bash ./guardrails_api/start.sh
2323

2424
infra:
2525
docker compose --profile infra up --build
@@ -37,15 +37,16 @@ refresh:
3737

3838

3939
format:
40-
ruff check app.py src/ tests/ --fix
41-
ruff format app.py src/ tests/
40+
ruff check guardrails_api/ tests/ --fix
41+
ruff format guardrails_api/ tests/
4242

4343

4444
lint:
45-
ruff check app.py src/ tests/
46-
ruff format app.py src/ tests/
45+
ruff check guardrails_api/ tests/
46+
ruff format guardrails_api/ tests/
4747

4848
qa:
49+
make build
4950
make lint
5051
make test-cov
5152

@@ -57,10 +58,10 @@ test:
5758
pytest ./tests
5859

5960
test-cov:
60-
coverage run --source=./src -m pytest ./tests
61-
coverage report --fail-under=50
61+
coverage run --source=./guardrails_api -m pytest ./tests
62+
coverage report --fail-under=45
6263

6364
view-test-cov:
64-
coverage run --source=./src -m pytest ./tests
65+
coverage run --source=./guardrails_api -m pytest ./tests
6566
coverage html
6667
open htmlcov/index.html

default.env

-7
This file was deleted.

guardrails_api/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.0.0"
1+
__version__ = "0.0.0"

guardrails_api/app.py

+22-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
from typing import Optional
23
from flask import Flask
34
from flask_cors import CORS
45
from werkzeug.middleware.proxy_fix import ProxyFix
@@ -20,12 +21,30 @@ def __call__(self, environ, start_response):
2021
return self.app(environ, start_response)
2122

2223

23-
def create_app():
24+
def register_config(config: Optional[str] = None):
25+
default_config_file = os.path.join(os.path.dirname(__file__), "default_config.py")
26+
27+
config_file = config or default_config_file
28+
config_file_path = os.path.abspath(config_file)
29+
if os.path.isfile(config_file_path):
30+
from importlib.machinery import SourceFileLoader
31+
32+
# This creates a module named "validators" with the contents of the init file
33+
# This allow statements like `from validators import StartsWith`
34+
# But more importantly, it registers all of the validators imported in the init
35+
SourceFileLoader("config", config_file_path).load_module()
36+
37+
38+
def create_app(env: Optional[str] = None, config: Optional[str] = None):
2439
if os.environ.get("APP_ENVIRONMENT") != "production":
2540
from dotenv import load_dotenv
2641

27-
# TODO: Accept this as an env var
28-
load_dotenv()
42+
default_env_file = os.path.join(os.path.dirname(__file__), "default.env")
43+
env_file = env or default_env_file
44+
env_file_path = os.path.abspath(env_file)
45+
load_dotenv(env_file_path)
46+
47+
register_config(config)
2948

3049
app = Flask(__name__)
3150

guardrails_api/blueprints/guards.py

+2-9
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
from guardrails_api.clients.postgres_client import postgres_is_enabled
1717
from guardrails_api.utils.handle_error import handle_error
1818
from guardrails_api.utils.get_llm_callable import get_llm_callable
19-
from guardrails_api.utils.prep_environment import cleanup_environment, prep_environment
2019

2120

2221
guards_bp = Blueprint("guards", __name__, url_prefix="/guards")
@@ -27,9 +26,8 @@
2726
guard_client = PGGuardClient()
2827
else:
2928
guard_client = MemoryGuardClient()
30-
# TODO: Accept file path as env var and dynamically import
31-
# read in guards from file
32-
import config
29+
# Will be defined at runtime
30+
import config # noqa
3331

3432
exports = config.__dir__()
3533
for export_name in exports:
@@ -180,9 +178,6 @@ def validate(guard_name: str):
180178
)
181179
decoded_guard_name = unquote_plus(guard_name)
182180
guard_struct = guard_client.get_guard(decoded_guard_name)
183-
if isinstance(guard_struct, GuardStruct):
184-
# TODO: is there a way to do this with Guard?
185-
prep_environment(guard_struct)
186181

187182
llm_output = payload.pop("llmOutput", None)
188183
num_reasks = payload.pop("numReasks", guard_struct.num_reasks)
@@ -327,6 +322,4 @@ def validate_streamer(guard_iter):
327322
# prompt_params=prompt_params,
328323
# result=result
329324
# )
330-
if isinstance(guard_struct, GuardStruct):
331-
cleanup_environment(guard_struct)
332325
return validation_output.to_response()

guardrails_api/blueprints/root.py

+3-6
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import flask
44
from string import Template
55
from flask import Blueprint
6+
from guardrails_api.open_api_spec import get_open_api_spec
67
from sqlalchemy import text
78
from guardrails_api.classes.health_check import HealthCheck
89
from guardrails_api.clients.postgres_client import PostgresClient, postgres_is_enabled
@@ -11,7 +12,6 @@
1112

1213

1314
root_bp = Blueprint("root", __name__, url_prefix="/")
14-
cached_api_spec = None
1515

1616

1717
@root_bp.route("/")
@@ -42,11 +42,8 @@ def health_check():
4242
@root_bp.route("/api-docs")
4343
@handle_error
4444
def api_docs():
45-
global cached_api_spec
46-
if not cached_api_spec:
47-
with open("./open-api-spec.json") as api_spec_file:
48-
cached_api_spec = json.loads(api_spec_file.read())
49-
return json.dumps(cached_api_spec)
45+
api_spec = get_open_api_spec()
46+
return json.dumps(api_spec)
5047

5148

5249
@root_bp.route("/docs")

guardrails_api/cli/__init__.py

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import typer
2+
import guardrails_api.cli.start # noqa
3+
from typing import Optional
4+
from guardrails_api import __version__
5+
from guardrails_api.cli.cli import cli
6+
7+
8+
def version_callback(value: bool):
9+
if value:
10+
print(f"guardrails-api CLI Version: {__version__}")
11+
raise typer.Exit()
12+
13+
14+
@cli.callback()
15+
def main(
16+
version: Optional[bool] = typer.Option(
17+
None, "--version", callback=version_callback, is_eager=True
18+
),
19+
):
20+
pass
21+
22+
23+
if __name__ == "__main__":
24+
cli()

guardrails_api/cli/cli.py

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import typer
2+
3+
4+
cli = typer.Typer()

guardrails_api/cli/start.py

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import typer
2+
from typing import Optional
3+
from gunicorn.app.base import BaseApplication
4+
from guardrails_api.cli.cli import cli
5+
from guardrails_api.app import create_app
6+
7+
8+
class StandaloneApplication(BaseApplication):
9+
def __init__(self, app, options=None):
10+
self.options = options or {}
11+
self.application = app
12+
super().__init__()
13+
14+
def load_config(self):
15+
config = {
16+
key: value
17+
for key, value in self.options.items()
18+
if key in self.cfg.settings and value is not None
19+
}
20+
for key, value in config.items():
21+
self.cfg.set(key.lower(), value)
22+
23+
def load(self):
24+
return self.application
25+
26+
27+
@cli.command("start")
28+
def start(
29+
env: Optional[str] = typer.Option(
30+
default="",
31+
help="An env file to load environment variables from.",
32+
prompt=".env file (optional)",
33+
),
34+
config: Optional[str] = typer.Option(
35+
default="",
36+
help="A config file to load Guards from.",
37+
prompt="config file (optional)",
38+
),
39+
):
40+
# TODO: If these are empty,
41+
# look for them in a .guardrailsrc in the current directory.
42+
env = env or None
43+
config = config or None
44+
45+
options = {
46+
"bind": "0.0.0.0:8000",
47+
"timeout": 5,
48+
"threads": 10,
49+
}
50+
StandaloneApplication(create_app(env, config), options).run()
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
'''
1+
"""
22
All guards defined here will be initialized, if and only if
33
the application is using in memory guards.
44
55
The application will use in memory guards if pg_host is left
6-
undefined. Otherwise, a postgres instance will be started
6+
undefined. Otherwise, a postgres instance will be started
77
and guards will be persisted into postgres. In that case,
88
these guards will not be initialized.
9-
'''
9+
"""
1010

11-
from guardrails import Guard
11+
from guardrails import Guard # noqa

guardrails_api/models/__init__.py

Whitespace-only changes.

guardrails_api/open_api_spec.py

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import json
2+
import os
3+
4+
open_api_spec = None
5+
6+
7+
def get_open_api_spec():
8+
global open_api_spec
9+
if not open_api_spec:
10+
api_spec_file_path = os.path.abspath(
11+
os.path.join(os.path.dirname(__file__), "open-api-spec.json")
12+
)
13+
14+
with open(api_spec_file_path) as api_spec_file:
15+
open_api_spec = json.loads(api_spec_file.read())
16+
17+
return open_api_spec

guardrails_api/start.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES
1010
curl https://raw.githubusercontent.com/guardrails-ai/guardrails-api-client/main/service-specs/guardrails-service-spec.yml -o ./open-api-spec.yml
1111
npx @redocly/cli bundle --dereferenced --output ./open-api-spec.json --ext json ./open-api-spec.yml
1212

13-
gunicorn --bind 0.0.0.0:8000 --timeout=5 --threads=10 "app:create_app()"
13+
gunicorn --bind 0.0.0.0:8000 --timeout=5 --threads=10 "guardrails_api.app:create_app()"

guardrails_api/utils/payload_validator.py

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1-
import json
21
from jsonschema import Draft202012Validator, ValidationError
32
from referencing import Registry, jsonschema as jsonschema_ref
3+
from guardrails_api.open_api_spec import get_open_api_spec
44
from guardrails_api.classes.http_error import HttpError
55
from guardrails_api.utils.remove_nones import remove_nones
66

7-
with open("./open-api-spec.json") as api_spec_file:
8-
api_spec = json.loads(api_spec_file.read())
9-
7+
api_spec = get_open_api_spec()
108
registry = Registry().with_resources(
119
[
1210
(

0 commit comments

Comments
 (0)