Skip to content

Commit 6c44fd6

Browse files
authored
Merge pull request #886 from basedosdados/feat/columns_api
Feat/columns api
2 parents 348bfd5 + 6605f68 commit 6c44fd6

File tree

3 files changed

+117
-3
lines changed

3 files changed

+117
-3
lines changed

backend/apps/api/v1/models.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1226,6 +1226,11 @@ def temporal_coverage(self) -> dict:
12261226
"""Temporal coverage"""
12271227
return get_temporal_coverage([self])
12281228

1229+
@property
1230+
def temporal_coverage_from_table(self) -> dict:
1231+
"""Temporal coverage from table, used as fallback for columns."""
1232+
return self.temporal_coverage
1233+
12291234
@property
12301235
def full_temporal_coverage(self) -> dict:
12311236
"""Temporal coverage steps"""

backend/apps/api/v1/urls.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from backend.apps.api.v1.views import (
99
DatasetRedirectView,
1010
table_stats,
11+
columns_view,
1112
upload_columns,
1213
)
1314

@@ -31,4 +32,7 @@ def graphql_view():
3132
path("dataset_redirect/", DatasetRedirectView.as_view()),
3233
path("tables/stats/", table_stats),
3334
path("upload_columns/", upload_columns),
35+
path("columns/", columns_view),
36+
path("tables/<uuid:table_id>/columns/", columns_view),
37+
path("columns/<uuid:column_id>/", columns_view),
3438
]

backend/apps/api/v1/views.py

Lines changed: 108 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,23 @@
55
from urllib.parse import urlparse
66

77
import pandas as pd
8-
from django.http import HttpRequest, HttpResponseRedirect, JsonResponse
8+
from django.core.serializers import serialize
9+
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect, JsonResponse
910
from django.views import View
1011

1112
from datetime import timedelta
1213

1314
from django.db.models import Sum
1415
from django.utils import timezone
1516

16-
from backend.apps.api.v1.models import BigQueryType, CloudTable, Column, Dataset, Table
17+
from backend.apps.api.v1.models import (
18+
BigQueryType,
19+
CloudTable,
20+
Column,
21+
get_temporal_coverage,
22+
Dataset,
23+
Table,
24+
)
1725

1826
URL_MAPPING = {
1927
"localhost:8080": "http://localhost:3000",
@@ -160,4 +168,101 @@ def table_stats(request: HttpRequest):
160168
"total_rows": aggregates["total_rows"] or 0,
161169
}
162170

163-
return JsonResponse(data)
171+
return JsonResponse(data)
172+
173+
def columns_view(request: HttpRequest, table_id: str = None, column_id: str = None):
174+
"""
175+
A simple REST API view for Columns.
176+
"""
177+
if column_id:
178+
try:
179+
column = Column.objects.select_related(
180+
"table", "table__dataset", "bigquery_type"
181+
).get(id=column_id)
182+
data = serialize(
183+
"json",
184+
[column],
185+
fields=(
186+
"name",
187+
"description",
188+
"bigquery_type",
189+
"is_primary_key",
190+
"table",
191+
),
192+
)
193+
return HttpResponse(data, content_type="application/json")
194+
except Column.DoesNotExist:
195+
return JsonResponse({"error": "Column not found"}, status=404)
196+
elif table_id:
197+
columns = (
198+
Column.objects.filter(table_id=table_id)
199+
.select_related(
200+
"table",
201+
"table__dataset",
202+
"bigquery_type",
203+
"directory_primary_key__table__dataset",
204+
)
205+
.prefetch_related(
206+
"directory_primary_key__table__cloud_tables",
207+
"coverages__datetime_ranges"
208+
)
209+
.order_by("order")
210+
)
211+
212+
if not columns.exists():
213+
return JsonResponse({"error": "Table not found or has no columns"}, status=404)
214+
215+
results = []
216+
table_temporal_coverage = None
217+
for col in columns:
218+
col_data = {
219+
"id": str(col.id),
220+
"order": col.order,
221+
"name": col.name,
222+
"description": col.description,
223+
"bigquery_type": {"name": col.bigquery_type.name if col.bigquery_type else None},
224+
"is_primary_key": col.is_primary_key,
225+
"covered_by_dictionary": col.covered_by_dictionary,
226+
"measurement_unit": col.measurement_unit,
227+
"contains_sensitive_data": col.contains_sensitive_data,
228+
"observations": col.observations,
229+
"temporal_coverage": None,
230+
"directory_primary_key": None,
231+
}
232+
233+
col_coverage = get_temporal_coverage([col])
234+
if not col_coverage.get("start") and not col_coverage.get("end"):
235+
if table_temporal_coverage is None:
236+
table_temporal_coverage = col.table.temporal_coverage_from_table
237+
col_data["temporal_coverage"] = table_temporal_coverage
238+
else:
239+
col_data["temporal_coverage"] = col_coverage
240+
241+
if dpk := col.directory_primary_key:
242+
cloud_table = dpk.table.cloud_tables.first()
243+
col_data["directory_primary_key"] = {
244+
"id": str(dpk.id),
245+
"name": dpk.name,
246+
"table": {
247+
"id": str(dpk.table.id),
248+
"name": dpk.table.name,
249+
"is_closed": dpk.table.is_closed,
250+
"uncompressed_file_size": dpk.table.uncompressed_file_size,
251+
"dataset": {
252+
"id": str(dpk.table.dataset.id),
253+
"name": dpk.table.dataset.name,
254+
},
255+
"cloud_table": {"gcp_table_id": cloud_table.gcp_table_id, "gcp_dataset_id": cloud_table.gcp_dataset_id, "gcp_project_id": cloud_table.gcp_project_id} if cloud_table else None
256+
},
257+
}
258+
results.append(col_data)
259+
260+
return JsonResponse(results, safe=False)
261+
else:
262+
columns = Column.objects.all()[:100]
263+
data = serialize(
264+
"json",
265+
columns,
266+
fields=("id", "name", "table"),
267+
)
268+
return HttpResponse(data, content_type="application/json")

0 commit comments

Comments
 (0)