Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ For local development and testing, copy `env.example` to `.env` and populate it.

Running the API locally:

`uvicorn app.main:app --reload --host 0.0.0.0 --port 8000`
`uv run fastapi --env-file=.env fastapi dev`

To run in a docker container, make sure your .env file is setup then run:

`docker-compose up --build -d`

Testing:

`pytest`
`uv run --env-file=.env pytest --maxfail=4 --tb=short -v`
17 changes: 14 additions & 3 deletions app/carto.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,17 @@ def __init__(self):
"CARTO_TOKEN"
) # token passed in at runtime as env variable. Available at Keeper record "CARTO - New Platform"
assert self.public_token, "Carto token not provided"
self.auth_header = {"Authorization": f"Bearer {self.public_token}"}
self.headers = {
"Authorization": f"Bearer {self.public_token}",
"Cache-Control": "max-age=1800", # Default Carto Cache to 30 minutes to prevent stale responses
}

async def get_count(
self,
table: str | None,
where: str | None,
timeout: float,
no_cache: bool,
session: aiohttp.ClientSession,
request: Request,
**kwargs,
Expand All @@ -47,8 +51,11 @@ async def get_count(
q_where = psql.SQL(f"WHERE {where} ")
query = query + q_where
params = {"q": query.as_string()}
headers = self.headers
if no_cache:
headers['cache-control'] = "max-age=0"
async with session.get(
self.base_url, params=params, headers=self.auth_header, timeout=timeout
self.base_url, params=params, headers=headers, timeout=timeout
) as response:
return await self.normalize_rv_count(request, response)

Expand Down Expand Up @@ -82,6 +89,7 @@ async def get(
out_sr: int | None,
sql: str | None,
timeout: float,
no_cache: bool,
session: aiohttp.ClientSession,
request: Request,
**kwargs,
Expand Down Expand Up @@ -134,8 +142,11 @@ async def get(
query = psql.SQL(sql)
table_schema = None
params = {"q": query.as_string()}
headers = self.headers
if no_cache:
headers['cache-control'] = "max-age=0"
async with session.get(
self.base_url, params=params, headers=self.auth_header, timeout=timeout
self.base_url, params=params, headers=headers, timeout=timeout
) as response:
return await self.normalize_rv(request, response, table_schema, limit, sql)

Expand Down
49 changes: 29 additions & 20 deletions app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ async def lifespan(app: FastAPI):
schema_cache.check_latest_commit()
commit_check_task = create_task(schema_cache.loop_commit_check())
await session_manager.start()
assert schema_cache.cache
yield
commit_check_task.cancel()
await session_manager.stop()
Expand Down Expand Up @@ -87,79 +88,86 @@ async def get_data(
table: Annotated[
str | None,
Query(
description=f"""Name of table to retrieve. Either `table` or `sql`
parameter is required. Ignored if `sql` parameter is provided. Need an
example tale? Try `table=dor_parcel`.
description=f"""Name of table to retrieve. Either `table` or `sql`
parameter is required. Ignored if `sql` parameter is provided. Need an
example tale? Try `table=dor_parcel`.
{make_param_api_descriptions(api_manager, "table")}"""
),
] = None,
fields: Annotated[
str | None,
Query(
description=f"""List of fields to retrieve, taking the form
_field_1_,_field_2_,... To receive all fields, do not include this parameter.
Writing fields=* will return an error. Ignored if `sql` or
description=f"""List of fields to retrieve, taking the form
_field_1_,_field_2_,... To receive all fields, do not include this parameter.
Writing fields=* will return an error. Ignored if `sql` or
`count_only` parameters are provided.{make_param_api_descriptions(api_manager, "fields")}"""
),
] = None,
where: Annotated[
str | None,
Query(
description=f"""An SQL _WHERE_ clause to filter data. Ignored if
description=f"""An SQL _WHERE_ clause to filter data. Ignored if
`sql` parameter is provided.{make_param_api_descriptions(api_manager, "where")}"""
),
] = None,
limit: Annotated[
int | None,
Query(
description=f"""Limit to the number of records to return. AGO enforces
a limit specific to each table (frequently 2,000 records); for Carto, this API
enforces a limit of 1,000 records as Carto otherwise does not have
limits. Any user-provided limit smaller than those takes precedence.
a limit specific to each table (frequently 2,000 records); for Carto, this API
enforces a limit of 1,000 records as Carto otherwise does not have
limits. Any user-provided limit smaller than those takes precedence.
Ignored if `sql` or `count_only` paramaters are provided.{make_param_api_descriptions(api_manager, "limit")}"""
),
] = None,
out_sr: Annotated[
int,
Query(
description=f"""Spatial Reference to return geometric records in.
Default SRID is WGS84 (4326). Ignored if dataset is not geometric, or `sql` or `count_only`
description=f"""Spatial Reference to return geometric records in.
Default SRID is WGS84 (4326). Ignored if dataset is not geometric, or `sql` or `count_only`
parameters are provided.{make_param_api_descriptions(api_manager, "count_only")}"""
),
] = AbstractWorker.DEFAULT_SRID,
count_only: Annotated[
bool,
Query(
description=f"""Return record count of provided query. Ignored if
description=f"""Return record count of provided query. Ignored if
`sql` parameter is provided.{make_param_api_descriptions(api_manager, "count_only")}"""
),
] = False,
sql: Annotated[
str | None,
Query(
description=f"""Raw SQL string to use when retrieving data. Users
should request no more than ~2,000 rows to avoid an `HTTP 413` error.
Either `table` or `sql` parameter is required. Need an example? Try
description=f"""Raw SQL string to use when retrieving data. Users
should request no more than ~2,000 rows to avoid an `HTTP 413` error.
Either `table` or `sql` parameter is required. Need an example? Try
`sql=SELECT * FROM DOR_PARCEL LIMIT 10`.{make_param_api_descriptions(api_manager, "sql")}"""
),
] = None,
service: Annotated[
Service | None,
Query(
description="""Name of API service to use. If not provided, the first
API service to locate the table will be used. Ignored if `sql` parameter
description="""Name of API service to use. If not provided, the first
API service to locate the table will be used. Ignored if `sql` parameter
is provided."""
),
] = None,
timeout: Annotated[
float,
Query(
description="""Amount of time in seconds to wait for response from downstream APIs
description="""Amount of time in seconds to wait for response from downstream APIs
before raising a timeout error""",
gt=0,
lt=300,
),
] = 30,
no_cache: Annotated[
bool,
Query(
description=f"""Request fresh results from downstream APIs, ignoring any HTTP caching.
{make_param_api_descriptions(api_manager, "no_cache")}"""
),
] = False,
session: aiohttp.ClientSession = Depends(session_manager),
) -> ReturnJson | JSONResponse:
"""Use this endpoint to retrieve data from the available
Expand All @@ -178,11 +186,12 @@ async def get_data(
"out_sr": out_sr,
"count_only": count_only,
"sql": sql,
"token": token,
"session": session,
"timeout": timeout,
"no_cache": no_cache,
"request": request,
"schema_cache": schema_cache,
"token": token,
}
if sql:
if service and service.lower() != "carto":
Expand Down
Loading
Loading