Skip to content

Commit 89d1745

Browse files
committed
Adding specification API
1 parent 8b9d235 commit 89d1745

File tree

3 files changed

+79
-9
lines changed

3 files changed

+79
-9
lines changed

README.md

+26-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ In order to build and test the software outside of Docker, you will need
1919

2020
You can run the API locally by running either `make compose-up` or `docker compose up -d --build`.
2121

22-
The docker compose setup runs the S3 locally using Localstack as well as the API. An S3 bucket called local-collection-data is created and seeded with example issue log data.
22+
The docker compose setup runs the S3 locally using Localstack as well as the API. An S3 bucket called local-collection-data is created and seeded with example files in the collection-data directory.
2323

2424

2525
## Swagger UI
@@ -69,3 +69,28 @@ Request for issues for a specific dataset and resource:
6969
curl http://localhost:8000/log/issue?dataset=border&resource=4a57239e3c1174c80b6d4a0278ab386a7c3664f2e985b2e07a66bbec84988b30&field=geometry
7070
```
7171

72+
### provision_summary endpoint
73+
74+
can be accessed via
75+
```
76+
http://localhost:8000/performance/provision_summary?organisation=local-authority:LBH&offset=50&limit=100
77+
```
78+
79+
Optional Parameters:
80+
* Offset
81+
* Limit
82+
* Organisation
83+
* Dataset
84+
85+
86+
### specification endpoint
87+
88+
can be accessed via
89+
```
90+
http://localhost:8000/specification/specification?offset=0&limit=10
91+
```
92+
93+
Optional Parameters:
94+
* Offset
95+
* Limit
96+
* Dataset

src/db.py

+29-5
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from schema import IssuesParams, ProvisionParams, SpecificationsParams
44
from pagination_model import PaginationParams, PaginatedResult
55
from config import config
6-
6+
import json
77

88
logger = get_logger(__name__)
99

@@ -101,12 +101,23 @@ def get_specification(params: SpecificationsParams):
101101
pagination = f"LIMIT {params.limit} OFFSET {params.offset}"
102102

103103
where_clause = ""
104+
104105
if params.dataset:
105-
where_clause += _add_condition(where_clause, f"dataset = '{params.dataset}'")
106+
where_clause += _add_condition(
107+
where_clause,
108+
f"TRIM(BOTH '\"' FROM json_extract(json(value), '$.dataset')) = '{params.dataset}'",
109+
)
106110

107-
sql_count = f"SELECT COUNT(*) FROM '{s3_uri}' {where_clause}"
111+
sql_count = f"""
112+
SELECT COUNT(*) FROM (SELECT unnest(CAST(json AS VARCHAR[])) AS value FROM '{s3_uri}')
113+
as parsed_json {where_clause} {pagination}
114+
"""
108115
logger.debug(sql_count)
109-
sql_results = f"SELECT * FROM '{s3_uri}' {where_clause} {pagination}"
116+
sql_results = f"""
117+
SELECT value as json FROM
118+
(SELECT unnest(CAST(json AS VARCHAR[])) AS value FROM '{s3_uri}')
119+
as parsed_json {where_clause} {pagination}
120+
"""
110121
logger.debug(sql_results)
111122

112123
with duckdb.connect() as conn:
@@ -118,14 +129,27 @@ def get_specification(params: SpecificationsParams):
118129
).fetchall()
119130
)
120131
logger.debug(conn.execute("FROM duckdb_secrets();").fetchall())
132+
121133
count = conn.execute(sql_count).fetchone()[
122134
0
123135
] # Count is first item in Tuple
124136
results = conn.execute(sql_results).arrow().to_pylist()
137+
138+
# Extract and parse the JSON field
139+
json_results = []
140+
for item in results:
141+
logger.error(item)
142+
if "json" in item and isinstance(item["json"], str):
143+
try:
144+
parsed_json = json.loads(item["json"])
145+
json_results.append(parsed_json)
146+
except json.JSONDecodeError:
147+
logger.warning(f"Invalid JSON format in row: {item['json']}")
148+
125149
return PaginatedResult(
126150
params=PaginationParams(offset=params.offset, limit=params.limit),
127151
total_results_available=count,
128-
data=results,
152+
data=json_results,
129153
)
130154
except Exception as e:
131155
logger.exception(

tests/integration/test_main.py

+24-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from fastapi.testclient import TestClient
22
from main import app
3-
import json
43

54
# Create a test client for the FastAPI app
65
client = TestClient(app)
@@ -88,8 +87,30 @@ def test_specification(s3_bucket):
8887

8988
response_data = response.json()
9089
assert "X-Pagination-Total-Results" in response.headers
91-
assert response.headers["X-Pagination-Total-Results"] == str(16)
90+
assert response.headers["X-Pagination-Total-Results"] == str(36)
9291
assert response.headers["X-Pagination-Limit"] == "8"
9392

9493
assert len(response_data) > 0
95-
assert response_data[0]["name"] == "Article 4 direction"
94+
95+
def test_specification_with_dataset(s3_bucket):
96+
# Prepare test params
97+
params = {
98+
"offset": 0,
99+
"limit": 8,
100+
"dataset": "article-4-direction-area",
101+
}
102+
103+
response = client.get("/specification/specification", params=params)
104+
105+
# Validate the results from the search
106+
assert response.status_code == 200
107+
108+
response_data = response.json()
109+
assert "X-Pagination-Total-Results" in response.headers
110+
assert response.headers["X-Pagination-Total-Results"] == str(1)
111+
assert response.headers["X-Pagination-Limit"] == "8"
112+
113+
assert len(response_data) > 0
114+
assert response_data[0]["dataset"] == "article-4-direction-area"
115+
assert response_data[0]["fields"]
116+
assert len(response_data[0]["fields"]) > 1

0 commit comments

Comments
 (0)