-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Move example defaults to app code - Add self-documenting `_seconds` prefix to relevant settings - Add configurable entrypoint - Add ability to write defaults to `CONFIG_PATH` - Improve code structure - Update documentation
- Loading branch information
Showing
11 changed files
with
281 additions
and
139 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,4 +15,5 @@ checkstyle.txt | |
.idea | ||
*.iml | ||
__pycache__ | ||
*.egg-info | ||
*.egg-info | ||
config.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,95 +1,17 @@ | ||
from contextlib import suppress | ||
from datetime import datetime | ||
import uvicorn | ||
|
||
import httpx | ||
import structlog | ||
from fastapi import FastAPI, Header | ||
from fastapi.middleware.cors import CORSMiddleware | ||
from fastapi.middleware.gzip import GZipMiddleware | ||
from fastapi.responses import ORJSONResponse | ||
from edge_proxy.settings import ensure_defaults, get_settings | ||
|
||
from fastapi_utils.tasks import repeat_every | ||
|
||
from edge_proxy.cache import LocalMemEnvironmentsCache | ||
from edge_proxy.environments import EnvironmentService | ||
from edge_proxy.exceptions import FeatureNotFoundError, FlagsmithUnknownKeyError | ||
from edge_proxy.logging import setup_logging | ||
from edge_proxy.models import IdentityWithTraits | ||
from edge_proxy.settings import Settings | ||
|
||
settings = Settings() | ||
setup_logging(settings.logging) | ||
environment_service = EnvironmentService( | ||
LocalMemEnvironmentsCache(), | ||
httpx.AsyncClient(timeout=settings.api_poll_timeout), | ||
settings, | ||
) | ||
app = FastAPI() | ||
|
||
|
||
@app.exception_handler(FlagsmithUnknownKeyError) | ||
async def unknown_key_error(request, exc): | ||
return ORJSONResponse( | ||
status_code=401, | ||
content={ | ||
"status": "unauthorized", | ||
"message": f"unknown key {exc}", | ||
}, | ||
def serve(): | ||
settings = get_settings() | ||
uvicorn.run( | ||
"edge_proxy.server:app", | ||
host=str(settings.server.host), | ||
port=settings.server.port, | ||
reload=settings.server.reload, | ||
) | ||
|
||
|
||
@app.get("/health", response_class=ORJSONResponse, deprecated=True) | ||
@app.get("/proxy/health", response_class=ORJSONResponse) | ||
async def health_check(): | ||
with suppress(TypeError): | ||
last_updated = datetime.now() - environment_service.last_updated_at | ||
buffer = 30 * len(settings.environment_key_pairs) # 30s per environment | ||
if last_updated.total_seconds() <= settings.api_poll_frequency + buffer: | ||
return ORJSONResponse(status_code=200, content={"status": "ok"}) | ||
|
||
return ORJSONResponse(status_code=500, content={"status": "error"}) | ||
|
||
|
||
@app.get("/api/v1/flags/", response_class=ORJSONResponse) | ||
async def flags(feature: str = None, x_environment_key: str = Header(None)): | ||
try: | ||
data = environment_service.get_flags_response_data(x_environment_key, feature) | ||
except FeatureNotFoundError: | ||
return ORJSONResponse( | ||
status_code=404, | ||
content={ | ||
"status": "not_found", | ||
"message": f"feature '{feature}' not found", | ||
}, | ||
) | ||
|
||
return ORJSONResponse(data) | ||
|
||
|
||
@app.post("/api/v1/identities/", response_class=ORJSONResponse) | ||
async def identity( | ||
input_data: IdentityWithTraits, | ||
x_environment_key: str = Header(None), | ||
): | ||
data = environment_service.get_identity_response_data(input_data, x_environment_key) | ||
return ORJSONResponse(data) | ||
|
||
|
||
@app.on_event("startup") | ||
@repeat_every( | ||
seconds=settings.api_poll_frequency, | ||
raise_exceptions=True, | ||
logger=structlog.get_logger(__name__), | ||
) | ||
async def refresh_cache(): | ||
await environment_service.refresh_environment_caches() | ||
|
||
|
||
app.add_middleware( | ||
CORSMiddleware, | ||
allow_origins=settings.allow_origins, | ||
allow_credentials=True, | ||
allow_methods=["*"], | ||
allow_headers=["*"], | ||
) | ||
app.add_middleware(GZipMiddleware, minimum_size=1000) | ||
def config(): | ||
ensure_defaults() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
from contextlib import suppress | ||
from datetime import datetime | ||
|
||
import httpx | ||
import structlog | ||
from fastapi import FastAPI, Header | ||
from fastapi.middleware.cors import CORSMiddleware | ||
from fastapi.middleware.gzip import GZipMiddleware | ||
from fastapi.responses import ORJSONResponse | ||
|
||
from fastapi_utils.tasks import repeat_every | ||
|
||
from edge_proxy.cache import LocalMemEnvironmentsCache | ||
from edge_proxy.environments import EnvironmentService | ||
from edge_proxy.exceptions import FeatureNotFoundError, FlagsmithUnknownKeyError | ||
from edge_proxy.logging import setup_logging | ||
from edge_proxy.models import IdentityWithTraits | ||
from edge_proxy.settings import get_settings | ||
|
||
settings = get_settings() | ||
setup_logging(settings.logging) | ||
environment_service = EnvironmentService( | ||
LocalMemEnvironmentsCache(), | ||
httpx.AsyncClient(timeout=settings.api_poll_timeout_seconds), | ||
settings, | ||
) | ||
app = FastAPI() | ||
|
||
|
||
@app.exception_handler(FlagsmithUnknownKeyError) | ||
async def unknown_key_error(request, exc): | ||
return ORJSONResponse( | ||
status_code=401, | ||
content={ | ||
"status": "unauthorized", | ||
"message": f"unknown key {exc}", | ||
}, | ||
) | ||
|
||
|
||
@app.get("/health", response_class=ORJSONResponse, deprecated=True) | ||
@app.get("/proxy/health", response_class=ORJSONResponse) | ||
async def health_check(): | ||
with suppress(TypeError): | ||
last_updated = datetime.now() - environment_service.last_updated_at | ||
buffer = 30 * len(settings.environment_key_pairs) # 30s per environment | ||
if last_updated.total_seconds() <= settings.api_poll_frequency_seconds + buffer: | ||
return ORJSONResponse(status_code=200, content={"status": "ok"}) | ||
|
||
return ORJSONResponse(status_code=500, content={"status": "error"}) | ||
|
||
|
||
@app.get("/api/v1/flags/", response_class=ORJSONResponse) | ||
async def flags(feature: str = None, x_environment_key: str = Header(None)): | ||
try: | ||
data = environment_service.get_flags_response_data(x_environment_key, feature) | ||
except FeatureNotFoundError: | ||
return ORJSONResponse( | ||
status_code=404, | ||
content={ | ||
"status": "not_found", | ||
"message": f"feature '{feature}' not found", | ||
}, | ||
) | ||
|
||
return ORJSONResponse(data) | ||
|
||
|
||
@app.post("/api/v1/identities/", response_class=ORJSONResponse) | ||
async def identity( | ||
input_data: IdentityWithTraits, | ||
x_environment_key: str = Header(None), | ||
): | ||
data = environment_service.get_identity_response_data(input_data, x_environment_key) | ||
return ORJSONResponse(data) | ||
|
||
|
||
@app.on_event("startup") | ||
@repeat_every( | ||
seconds=settings.api_poll_frequency_seconds, | ||
raise_exceptions=True, | ||
logger=structlog.get_logger(__name__), | ||
) | ||
async def refresh_cache(): | ||
await environment_service.refresh_environment_caches() | ||
|
||
|
||
app.add_middleware( | ||
CORSMiddleware, | ||
allow_origins=settings.allow_origins, | ||
allow_credentials=True, | ||
allow_methods=["*"], | ||
allow_headers=["*"], | ||
) | ||
app.add_middleware(GZipMiddleware, minimum_size=1000) |
Oops, something went wrong.