local-bigquery 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- local_bigquery/__init__.py +3 -0
- local_bigquery/api/__init__.py +30 -0
- local_bigquery/api/datasets.py +59 -0
- local_bigquery/api/iam.py +29 -0
- local_bigquery/api/jobs.py +176 -0
- local_bigquery/api/models.py +40 -0
- local_bigquery/api/projects.py +35 -0
- local_bigquery/api/routines.py +81 -0
- local_bigquery/api/row_access_policies.py +73 -0
- local_bigquery/api/tables.py +115 -0
- local_bigquery/api/uploads.py +80 -0
- local_bigquery/app.py +145 -0
- local_bigquery/catalog/__init__.py +0 -0
- local_bigquery/catalog/datasets.py +119 -0
- local_bigquery/catalog/ddl.py +215 -0
- local_bigquery/catalog/iam.py +68 -0
- local_bigquery/catalog/indexes.py +59 -0
- local_bigquery/catalog/metadata.py +112 -0
- local_bigquery/catalog/models.py +102 -0
- local_bigquery/catalog/names.py +83 -0
- local_bigquery/catalog/options.py +102 -0
- local_bigquery/catalog/routines.py +144 -0
- local_bigquery/catalog/row_access.py +174 -0
- local_bigquery/catalog/tabledata.py +203 -0
- local_bigquery/catalog/tables.py +430 -0
- local_bigquery/cli.py +42 -0
- local_bigquery/discovery.json +11957 -0
- local_bigquery/engine/__init__.py +0 -0
- local_bigquery/engine/database.py +159 -0
- local_bigquery/engine/results.py +102 -0
- local_bigquery/engine/sessions.py +40 -0
- local_bigquery/engine/types.py +222 -0
- local_bigquery/errors.py +287 -0
- local_bigquery/grpc/__init__.py +0 -0
- local_bigquery/grpc/read.py +258 -0
- local_bigquery/grpc/server.py +121 -0
- local_bigquery/grpc/write.py +301 -0
- local_bigquery/jobs/__init__.py +0 -0
- local_bigquery/jobs/copy.py +46 -0
- local_bigquery/jobs/extract.py +95 -0
- local_bigquery/jobs/load.py +281 -0
- local_bigquery/jobs/merge.py +139 -0
- local_bigquery/jobs/query.py +428 -0
- local_bigquery/jobs/runner.py +243 -0
- local_bigquery/jobs/storage.py +81 -0
- local_bigquery/jobs/store.py +61 -0
- local_bigquery/models.py +2104 -0
- local_bigquery/pytest_plugin.py +59 -0
- local_bigquery/repl.py +125 -0
- local_bigquery/resource.py +41 -0
- local_bigquery/settings.py +23 -0
- local_bigquery/sql/__init__.py +0 -0
- local_bigquery/sql/dialect.py +241 -0
- local_bigquery/sql/functions/aggregates.sql +24 -0
- local_bigquery/sql/functions/arrays.sql +23 -0
- local_bigquery/sql/functions/conditional.sql +1 -0
- local_bigquery/sql/functions/datetime.sql +68 -0
- local_bigquery/sql/functions/geography.sql +126 -0
- local_bigquery/sql/functions/json.sql +145 -0
- local_bigquery/sql/functions/math.sql +34 -0
- local_bigquery/sql/functions/net.sql +59 -0
- local_bigquery/sql/functions/ranges.sql +49 -0
- local_bigquery/sql/functions/strings.sql +81 -0
- local_bigquery/sql/js.py +160 -0
- local_bigquery/sql/native.py +345 -0
- local_bigquery/sql/params.py +118 -0
- local_bigquery/sql/rules/__init__.py +49 -0
- local_bigquery/sql/rules/arrays.py +122 -0
- local_bigquery/sql/rules/columns.py +24 -0
- local_bigquery/sql/rules/datetime.py +419 -0
- local_bigquery/sql/rules/ddl.py +161 -0
- local_bigquery/sql/rules/dml.py +42 -0
- local_bigquery/sql/rules/external.py +46 -0
- local_bigquery/sql/rules/information_schema.py +604 -0
- local_bigquery/sql/rules/json.py +54 -0
- local_bigquery/sql/rules/literals.py +51 -0
- local_bigquery/sql/rules/math.py +91 -0
- local_bigquery/sql/rules/parameters.py +70 -0
- local_bigquery/sql/rules/partitions.py +83 -0
- local_bigquery/sql/rules/query.py +140 -0
- local_bigquery/sql/rules/ranges.py +114 -0
- local_bigquery/sql/rules/row_access.py +71 -0
- local_bigquery/sql/rules/safe.py +42 -0
- local_bigquery/sql/rules/strings.py +148 -0
- local_bigquery/sql/rules/tables.py +221 -0
- local_bigquery/sql/rules/typing.py +319 -0
- local_bigquery/sql/rules/udfs.py +72 -0
- local_bigquery/sql/script.py +672 -0
- local_bigquery/sql/translate.py +62 -0
- local_bigquery-0.3.0.dist-info/METADATA +168 -0
- local_bigquery-0.3.0.dist-info/RECORD +94 -0
- local_bigquery-0.3.0.dist-info/WHEEL +4 -0
- local_bigquery-0.3.0.dist-info/entry_points.txt +5 -0
- local_bigquery-0.3.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter, Response
|
|
4
|
+
|
|
5
|
+
from local_bigquery.resource import Resource
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Router(APIRouter):
|
|
9
|
+
def add_api_route(self, path, endpoint, **kwargs):
|
|
10
|
+
super().add_api_route(
|
|
11
|
+
path, endpoint, **kwargs | {"response_model_exclude_unset": True}
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def paginate(
|
|
16
|
+
items: list, max_results: int | None, page_token: str | None
|
|
17
|
+
) -> tuple[list, str | None]:
|
|
18
|
+
start = int(page_token or 0)
|
|
19
|
+
end = len(items) if max_results is None else start + max_results
|
|
20
|
+
return items[start:end], str(end) if end < len(items) else None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def with_rows(payload: dict, rows: list[str]) -> Response:
|
|
24
|
+
body = json.dumps(
|
|
25
|
+
{key: value for key, value in payload.items() if value is not None},
|
|
26
|
+
default=Resource.dump,
|
|
27
|
+
)
|
|
28
|
+
if rows:
|
|
29
|
+
body = f'{body[:-1]}, "rows": [{",".join(rows)}]}}'
|
|
30
|
+
return Response(body, media_type="application/json")
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from fastapi import Header
|
|
2
|
+
|
|
3
|
+
from local_bigquery.api import Router, paginate
|
|
4
|
+
from local_bigquery.catalog import datasets
|
|
5
|
+
from local_bigquery.models import Dataset, DatasetList, DatasetListDatasetsItem
|
|
6
|
+
|
|
7
|
+
router = Router(tags=["datasets"])
|
|
8
|
+
SUMMARY_FIELDS = set(DatasetListDatasetsItem.model_fields)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@router.get("/projects/{project_id}/datasets")
|
|
12
|
+
def list_datasets(
|
|
13
|
+
project_id: str,
|
|
14
|
+
maxResults: int | None = None,
|
|
15
|
+
pageToken: str | None = None,
|
|
16
|
+
all: bool = False,
|
|
17
|
+
filter: str | None = None,
|
|
18
|
+
) -> DatasetList:
|
|
19
|
+
summaries = [
|
|
20
|
+
dataset.model_dump(include=SUMMARY_FIELDS, exclude_none=True)
|
|
21
|
+
for dataset in datasets.list_(project_id, filter, all)
|
|
22
|
+
]
|
|
23
|
+
page, token = paginate(summaries, maxResults, pageToken)
|
|
24
|
+
return DatasetList(kind="bigquery#datasetList", datasets=page, nextPageToken=token)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@router.post("/projects/{project_id}/datasets")
|
|
28
|
+
def insert_dataset(project_id: str, body: Dataset) -> Dataset:
|
|
29
|
+
return datasets.create(project_id, body)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@router.get("/projects/{project_id}/datasets/{dataset_id}")
|
|
33
|
+
def get_dataset(project_id: str, dataset_id: str) -> Dataset:
|
|
34
|
+
return datasets.get(project_id, dataset_id)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@router.patch("/projects/{project_id}/datasets/{dataset_id}")
|
|
38
|
+
def patch_dataset(
|
|
39
|
+
project_id: str,
|
|
40
|
+
dataset_id: str,
|
|
41
|
+
body: Dataset,
|
|
42
|
+
if_match: str | None = Header(None),
|
|
43
|
+
) -> Dataset:
|
|
44
|
+
return datasets.update(project_id, dataset_id, body, if_match, replace=False)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@router.put("/projects/{project_id}/datasets/{dataset_id}")
|
|
48
|
+
def update_dataset(
|
|
49
|
+
project_id: str,
|
|
50
|
+
dataset_id: str,
|
|
51
|
+
body: Dataset,
|
|
52
|
+
if_match: str | None = Header(None),
|
|
53
|
+
) -> Dataset:
|
|
54
|
+
return datasets.update(project_id, dataset_id, body, if_match, replace=True)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@router.delete("/projects/{project_id}/datasets/{dataset_id}", status_code=204)
|
|
58
|
+
def delete_dataset(project_id: str, dataset_id: str, deleteContents: bool = False):
|
|
59
|
+
datasets.delete(project_id, dataset_id, deleteContents)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from local_bigquery.api import Router
|
|
2
|
+
from local_bigquery.catalog import iam
|
|
3
|
+
from local_bigquery.models import (
|
|
4
|
+
Policy,
|
|
5
|
+
SetIamPolicyRequest,
|
|
6
|
+
TestIamPermissionsRequest,
|
|
7
|
+
TestIamPermissionsResponse,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
router = Router(tags=["iam"])
|
|
11
|
+
RESOURCE = "/{resource:path}"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@router.post(f"{RESOURCE}:getIamPolicy")
|
|
15
|
+
def get_policy(resource: str) -> Policy:
|
|
16
|
+
return iam.get(resource)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@router.post(f"{RESOURCE}:setIamPolicy")
|
|
20
|
+
def set_policy(resource: str, body: SetIamPolicyRequest) -> Policy:
|
|
21
|
+
return iam.set_(resource, body.policy or Policy())
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@router.post(f"{RESOURCE}:testIamPermissions")
|
|
25
|
+
def test_permissions(
|
|
26
|
+
resource: str, body: TestIamPermissionsRequest
|
|
27
|
+
) -> TestIamPermissionsResponse:
|
|
28
|
+
iam.get(resource)
|
|
29
|
+
return TestIamPermissionsResponse(permissions=body.permissions or [])
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
|
|
3
|
+
from fastapi import Query
|
|
4
|
+
|
|
5
|
+
from local_bigquery.api import Router, paginate, with_rows
|
|
6
|
+
from local_bigquery.catalog import tabledata, tables
|
|
7
|
+
from local_bigquery.jobs import runner, store
|
|
8
|
+
from local_bigquery.models import (
|
|
9
|
+
Job,
|
|
10
|
+
JobCancelResponse,
|
|
11
|
+
JobConfiguration,
|
|
12
|
+
JobList,
|
|
13
|
+
JobListJobsItem,
|
|
14
|
+
QueryRequest,
|
|
15
|
+
TableSchema,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
router = Router(tags=["jobs"])
|
|
19
|
+
LIST_FIELDS = set(JobListJobsItem.model_fields)
|
|
20
|
+
QUERY_CONFIGURATION = ("dryRun", "labels", "jobTimeoutMs", "jobCreationMode")
|
|
21
|
+
QUERY_ONLY = ("kind", "formatOptions", "timeoutMs")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def results(
|
|
25
|
+
job: Job, max_results: int | None, start: int, int64_timestamps: bool
|
|
26
|
+
) -> tuple[dict, list[str]]:
|
|
27
|
+
payload = {"jobReference": job.jobReference, "jobComplete": False}
|
|
28
|
+
if job.status.state != "DONE":
|
|
29
|
+
return payload, []
|
|
30
|
+
if error := runner.error(job):
|
|
31
|
+
raise error
|
|
32
|
+
statistics = job.statistics.query
|
|
33
|
+
payload |= {
|
|
34
|
+
"jobComplete": True,
|
|
35
|
+
"cacheHit": False,
|
|
36
|
+
"totalBytesProcessed": "0",
|
|
37
|
+
"statementType": statistics.statementType,
|
|
38
|
+
"numDmlAffectedRows": statistics.numDmlAffectedRows,
|
|
39
|
+
"dmlStats": statistics.dmlStats,
|
|
40
|
+
"totalRows": "0",
|
|
41
|
+
}
|
|
42
|
+
destination = job.configuration.query.destinationTable
|
|
43
|
+
if not destination:
|
|
44
|
+
return payload | {"schema": statistics.schema_}, []
|
|
45
|
+
page, schema = tabledata.list_rows(
|
|
46
|
+
*tables.reference(destination), max_results, start, None, int64_timestamps
|
|
47
|
+
)
|
|
48
|
+
payload |= {
|
|
49
|
+
"schema": TableSchema(fields=schema),
|
|
50
|
+
"totalRows": str(page.total),
|
|
51
|
+
"pageToken": page.next_token,
|
|
52
|
+
}
|
|
53
|
+
return payload, page.rows
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@router.get("/projects/{project_id}/jobs")
|
|
57
|
+
def list_jobs(
|
|
58
|
+
project_id: str,
|
|
59
|
+
maxResults: int | None = None,
|
|
60
|
+
pageToken: str | None = None,
|
|
61
|
+
stateFilter: list[str] | None = Query(None),
|
|
62
|
+
parentJobId: str | None = None,
|
|
63
|
+
minCreationTime: int | None = None,
|
|
64
|
+
maxCreationTime: int | None = None,
|
|
65
|
+
) -> JobList:
|
|
66
|
+
states = [state.upper() for state in stateFilter] if stateFilter else None
|
|
67
|
+
jobs = [
|
|
68
|
+
JobListJobsItem.model_validate(
|
|
69
|
+
job.model_dump(include=LIST_FIELDS, exclude_none=True)
|
|
70
|
+
| {"state": job.status.state, "errorResult": job.status.errorResult}
|
|
71
|
+
)
|
|
72
|
+
for job in store.list_(
|
|
73
|
+
project_id, states, parentJobId, minCreationTime, maxCreationTime
|
|
74
|
+
)
|
|
75
|
+
]
|
|
76
|
+
page, token = paginate(jobs, maxResults, pageToken)
|
|
77
|
+
return JobList(kind="bigquery#jobList", jobs=page, nextPageToken=token)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@router.post("/projects/{project_id}/jobs")
|
|
81
|
+
def insert_job(project_id: str, body: Job) -> Job:
|
|
82
|
+
job_id = (body.jobReference and body.jobReference.jobId) or str(uuid.uuid4())
|
|
83
|
+
job = runner.submit(project_id, job_id, body.configuration or JobConfiguration())
|
|
84
|
+
if job.jobReference.jobId is None:
|
|
85
|
+
return job
|
|
86
|
+
return runner.submitted(project_id, job_id)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@router.get("/projects/{project_id}/jobs/{job_id}")
|
|
90
|
+
def get_job(project_id: str, job_id: str) -> Job:
|
|
91
|
+
return runner.get(project_id, job_id)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@router.post("/projects/{project_id}/jobs/{job_id}/cancel")
|
|
95
|
+
def cancel_job(project_id: str, job_id: str) -> JobCancelResponse:
|
|
96
|
+
job = runner.cancel(project_id, job_id)
|
|
97
|
+
return JobCancelResponse(kind="bigquery#jobCancelResponse", job=job)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@router.delete("/projects/{project_id}/jobs/{job_id}/delete")
|
|
101
|
+
def delete_job(project_id: str, job_id: str) -> dict:
|
|
102
|
+
job = runner.get(project_id, job_id)
|
|
103
|
+
config = job.configuration.query
|
|
104
|
+
destination = config and config.destinationTable
|
|
105
|
+
if destination and destination.datasetId == tables.RESULTS:
|
|
106
|
+
tables.delete(project_id, tables.RESULTS, job_id)
|
|
107
|
+
store.delete(project_id, job_id)
|
|
108
|
+
return {}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@router.post("/projects/{project_id}/queries")
|
|
112
|
+
def run_query(project_id: str, body: QueryRequest):
|
|
113
|
+
request = body.given()
|
|
114
|
+
configuration = JobConfiguration.model_validate(
|
|
115
|
+
{key: request[key] for key in QUERY_CONFIGURATION if key in request}
|
|
116
|
+
| {
|
|
117
|
+
"query": {
|
|
118
|
+
key: value
|
|
119
|
+
for key, value in request.items()
|
|
120
|
+
if key not in (*QUERY_CONFIGURATION, *QUERY_ONLY)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
)
|
|
124
|
+
job_id = body.requestId or str(uuid.uuid4())
|
|
125
|
+
job = runner.submit(project_id, job_id, configuration)
|
|
126
|
+
if job.jobReference.jobId is None:
|
|
127
|
+
statistics = job.statistics.query
|
|
128
|
+
return with_rows(
|
|
129
|
+
{
|
|
130
|
+
"kind": "bigquery#queryResponse",
|
|
131
|
+
"jobReference": job.jobReference,
|
|
132
|
+
"jobComplete": True,
|
|
133
|
+
"schema": statistics.schema_,
|
|
134
|
+
"totalBytesProcessed": "0",
|
|
135
|
+
"statementType": statistics.statementType,
|
|
136
|
+
},
|
|
137
|
+
[],
|
|
138
|
+
)
|
|
139
|
+
job = runner.wait(project_id, job_id, body.timeoutMs)
|
|
140
|
+
if error := runner.error(job):
|
|
141
|
+
raise runner.synchronous(error)
|
|
142
|
+
int64_timestamps = bool(body.formatOptions and body.formatOptions.useInt64Timestamp)
|
|
143
|
+
payload, rows = results(job, body.maxResults, 0, int64_timestamps)
|
|
144
|
+
statistics = job.statistics
|
|
145
|
+
return with_rows(
|
|
146
|
+
{"kind": "bigquery#queryResponse"}
|
|
147
|
+
| payload
|
|
148
|
+
| {
|
|
149
|
+
"queryId": job_id,
|
|
150
|
+
"location": "US",
|
|
151
|
+
"creationTime": statistics.creationTime,
|
|
152
|
+
"startTime": statistics.startTime,
|
|
153
|
+
"endTime": statistics.endTime,
|
|
154
|
+
"jobCreationReason": job.jobCreationReason,
|
|
155
|
+
"sessionInfo": statistics.sessionInfo,
|
|
156
|
+
"totalBytesBilled": "0",
|
|
157
|
+
"totalSlotMs": "0",
|
|
158
|
+
},
|
|
159
|
+
rows,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@router.get("/projects/{project_id}/queries/{job_id}")
|
|
164
|
+
def get_query_results(
|
|
165
|
+
project_id: str,
|
|
166
|
+
job_id: str,
|
|
167
|
+
maxResults: int | None = None,
|
|
168
|
+
pageToken: str | None = None,
|
|
169
|
+
startIndex: int = 0,
|
|
170
|
+
timeoutMs: int | None = None,
|
|
171
|
+
int64_timestamps: bool = Query(False, alias="formatOptions.useInt64Timestamp"),
|
|
172
|
+
):
|
|
173
|
+
job = runner.wait(project_id, job_id, timeoutMs)
|
|
174
|
+
start = int(pageToken) if pageToken else startIndex
|
|
175
|
+
payload, rows = results(job, maxResults, start, int64_timestamps)
|
|
176
|
+
return with_rows({"kind": "bigquery#getQueryResultsResponse"} | payload, rows)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from fastapi import Header
|
|
2
|
+
|
|
3
|
+
from local_bigquery.api import Router, paginate
|
|
4
|
+
from local_bigquery.catalog import models
|
|
5
|
+
from local_bigquery.models import ListModelsResponse, Model
|
|
6
|
+
|
|
7
|
+
router = Router(tags=["models"])
|
|
8
|
+
MODEL = "/projects/{project_id}/datasets/{dataset_id}/models/{model_id}"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@router.get("/projects/{project_id}/datasets/{dataset_id}/models")
|
|
12
|
+
def list_models(
|
|
13
|
+
project_id: str,
|
|
14
|
+
dataset_id: str,
|
|
15
|
+
maxResults: int | None = None,
|
|
16
|
+
pageToken: str | None = None,
|
|
17
|
+
) -> ListModelsResponse:
|
|
18
|
+
page, token = paginate(models.list_(project_id, dataset_id), maxResults, pageToken)
|
|
19
|
+
return ListModelsResponse(models=page, nextPageToken=token)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@router.get(MODEL)
|
|
23
|
+
def get_model(project_id: str, dataset_id: str, model_id: str) -> Model:
|
|
24
|
+
return models.get(project_id, dataset_id, model_id)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@router.patch(MODEL)
|
|
28
|
+
def patch_model(
|
|
29
|
+
project_id: str,
|
|
30
|
+
dataset_id: str,
|
|
31
|
+
model_id: str,
|
|
32
|
+
body: Model,
|
|
33
|
+
if_match: str | None = Header(None),
|
|
34
|
+
) -> Model:
|
|
35
|
+
return models.update(project_id, dataset_id, model_id, body, if_match)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@router.delete(MODEL, status_code=204)
|
|
39
|
+
def delete_model(project_id: str, dataset_id: str, model_id: str):
|
|
40
|
+
models.delete(project_id, dataset_id, model_id)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from local_bigquery.api import Router, paginate
|
|
2
|
+
from local_bigquery.engine import database
|
|
3
|
+
from local_bigquery.models import GetServiceAccountResponse, ProjectList
|
|
4
|
+
|
|
5
|
+
router = Router(tags=["projects"])
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@router.get("/projects")
|
|
9
|
+
def list_projects(
|
|
10
|
+
maxResults: int | None = None, pageToken: str | None = None
|
|
11
|
+
) -> ProjectList:
|
|
12
|
+
project_ids = database.projects()
|
|
13
|
+
page, token = paginate(project_ids, maxResults, pageToken)
|
|
14
|
+
return ProjectList(
|
|
15
|
+
kind="bigquery#projectList",
|
|
16
|
+
projects=[
|
|
17
|
+
{
|
|
18
|
+
"kind": "bigquery#project",
|
|
19
|
+
"id": project_id,
|
|
20
|
+
"numericId": str(abs(hash(project_id))),
|
|
21
|
+
"friendlyName": project_id,
|
|
22
|
+
"projectReference": {"projectId": project_id},
|
|
23
|
+
}
|
|
24
|
+
for project_id in page
|
|
25
|
+
],
|
|
26
|
+
nextPageToken=token,
|
|
27
|
+
totalItems=len(project_ids),
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@router.get("/projects/{project_id}/serviceAccount")
|
|
32
|
+
def get_service_account(project_id: str) -> GetServiceAccountResponse:
|
|
33
|
+
return GetServiceAccountResponse(
|
|
34
|
+
email=f"service-account@{project_id}.iam.gserviceaccount.com"
|
|
35
|
+
)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from fastapi import Header
|
|
2
|
+
|
|
3
|
+
from local_bigquery.api import Router, paginate
|
|
4
|
+
from local_bigquery.catalog import datasets, metadata, names, routines
|
|
5
|
+
from local_bigquery.errors import BigQueryError, already_exists
|
|
6
|
+
from local_bigquery.jobs.query import run_ddl
|
|
7
|
+
from local_bigquery.models import ListRoutinesResponse, Routine
|
|
8
|
+
|
|
9
|
+
router = Router(tags=["routines"])
|
|
10
|
+
ROUTINES = "/projects/{project_id}/datasets/{dataset_id}/routines"
|
|
11
|
+
ROUTINE = f"{ROUTINES}/{{routine_id}}"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def load(project_id: str, dataset_id: str, routine_id: str) -> Routine:
|
|
15
|
+
datasets.get(project_id, dataset_id)
|
|
16
|
+
return routines.get(project_id, dataset_id, routine_id)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def define(
|
|
20
|
+
project_id: str, dataset_id: str, routine_id: str, body: Routine, replace: bool
|
|
21
|
+
) -> Routine:
|
|
22
|
+
reference = {
|
|
23
|
+
"projectId": project_id,
|
|
24
|
+
"datasetId": dataset_id,
|
|
25
|
+
"routineId": routine_id,
|
|
26
|
+
}
|
|
27
|
+
body = body.replace(routineReference=reference)
|
|
28
|
+
run_ddl(project_id, routines.statement(body, replace))
|
|
29
|
+
stored = routines.load(project_id, dataset_id, routine_id)
|
|
30
|
+
return routines.save(
|
|
31
|
+
project_id, dataset_id, routine_id, stored.replace(**body.dump())
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@router.get(ROUTINES)
|
|
36
|
+
def list_routines(
|
|
37
|
+
project_id: str,
|
|
38
|
+
dataset_id: str,
|
|
39
|
+
maxResults: int | None = None,
|
|
40
|
+
pageToken: str | None = None,
|
|
41
|
+
) -> ListRoutinesResponse:
|
|
42
|
+
datasets.get(project_id, dataset_id)
|
|
43
|
+
page, token = paginate(
|
|
44
|
+
routines.list_(project_id, dataset_id), maxResults, pageToken
|
|
45
|
+
)
|
|
46
|
+
return ListRoutinesResponse(routines=page, nextPageToken=token)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@router.post(ROUTINES)
|
|
50
|
+
def insert_routine(project_id: str, dataset_id: str, body: Routine) -> Routine:
|
|
51
|
+
datasets.get(project_id, dataset_id)
|
|
52
|
+
routine_id = body.routineReference and body.routineReference.routineId
|
|
53
|
+
if routines.load(project_id, dataset_id, routine_id) is not None:
|
|
54
|
+
raise already_exists("Routine", names.label(project_id, dataset_id, routine_id))
|
|
55
|
+
return define(project_id, dataset_id, routine_id, body, False)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@router.get(ROUTINE)
|
|
59
|
+
def get_routine(project_id: str, dataset_id: str, routine_id: str) -> Routine:
|
|
60
|
+
return load(project_id, dataset_id, routine_id)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@router.put(ROUTINE)
|
|
64
|
+
def update_routine(
|
|
65
|
+
project_id: str,
|
|
66
|
+
dataset_id: str,
|
|
67
|
+
routine_id: str,
|
|
68
|
+
body: Routine,
|
|
69
|
+
if_match: str | None = Header(None),
|
|
70
|
+
) -> Routine:
|
|
71
|
+
if not body.routineType:
|
|
72
|
+
raise BigQueryError("invalid", "Routine type must be specified")
|
|
73
|
+
current = load(project_id, dataset_id, routine_id)
|
|
74
|
+
metadata.check_etag(current, if_match)
|
|
75
|
+
return define(project_id, dataset_id, routine_id, body, True)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@router.delete(ROUTINE, status_code=204)
|
|
79
|
+
def delete_routine(project_id: str, dataset_id: str, routine_id: str):
|
|
80
|
+
kind = routines.ROUTINE_TYPES[load(project_id, dataset_id, routine_id).routineType]
|
|
81
|
+
run_ddl(project_id, f"DROP {kind} `{project_id}.{dataset_id}.{routine_id}`")
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from local_bigquery.api import Router, paginate
|
|
2
|
+
from local_bigquery.catalog import row_access
|
|
3
|
+
from local_bigquery.models import (
|
|
4
|
+
BatchDeleteRowAccessPoliciesRequest,
|
|
5
|
+
ListRowAccessPoliciesResponse,
|
|
6
|
+
RowAccessPolicy,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
router = Router(tags=["rowAccessPolicies"])
|
|
10
|
+
POLICIES = (
|
|
11
|
+
"/projects/{project_id}/datasets/{dataset_id}/tables/{table_id}/rowAccessPolicies"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@router.get(POLICIES)
|
|
16
|
+
def list_policies(
|
|
17
|
+
project_id: str,
|
|
18
|
+
dataset_id: str,
|
|
19
|
+
table_id: str,
|
|
20
|
+
pageSize: int | None = None,
|
|
21
|
+
pageToken: str | None = None,
|
|
22
|
+
) -> ListRowAccessPoliciesResponse:
|
|
23
|
+
policies = row_access.list_(project_id, dataset_id, table_id)
|
|
24
|
+
page, token = paginate(policies, pageSize, pageToken)
|
|
25
|
+
return ListRowAccessPoliciesResponse(rowAccessPolicies=page, nextPageToken=token)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@router.post(POLICIES)
|
|
29
|
+
def insert_policy(
|
|
30
|
+
project_id: str, dataset_id: str, table_id: str, body: RowAccessPolicy
|
|
31
|
+
) -> RowAccessPolicy:
|
|
32
|
+
return row_access.save(project_id, dataset_id, table_id, body)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@router.post(f"{POLICIES}:batchDelete")
|
|
36
|
+
def batch_delete(
|
|
37
|
+
project_id: str,
|
|
38
|
+
dataset_id: str,
|
|
39
|
+
table_id: str,
|
|
40
|
+
body: BatchDeleteRowAccessPoliciesRequest,
|
|
41
|
+
) -> dict:
|
|
42
|
+
row_access.delete(project_id, dataset_id, table_id, *body.policyIds or [])
|
|
43
|
+
return {}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@router.get(f"{POLICIES}/{{policy_id}}")
|
|
47
|
+
def get_policy(
|
|
48
|
+
project_id: str, dataset_id: str, table_id: str, policy_id: str
|
|
49
|
+
) -> RowAccessPolicy:
|
|
50
|
+
return row_access.get(project_id, dataset_id, table_id, policy_id)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@router.put(f"{POLICIES}/{{policy_id}}")
|
|
54
|
+
def update_policy(
|
|
55
|
+
project_id: str,
|
|
56
|
+
dataset_id: str,
|
|
57
|
+
table_id: str,
|
|
58
|
+
policy_id: str,
|
|
59
|
+
body: RowAccessPolicy,
|
|
60
|
+
) -> RowAccessPolicy:
|
|
61
|
+
row_access.get(project_id, dataset_id, table_id, policy_id)
|
|
62
|
+
body = body.merged(
|
|
63
|
+
RowAccessPolicy(rowAccessPolicyReference={"policyId": policy_id})
|
|
64
|
+
)
|
|
65
|
+
return row_access.save(project_id, dataset_id, table_id, body, replace=True)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@router.delete(f"{POLICIES}/{{policy_id}}")
|
|
69
|
+
def delete_policy(
|
|
70
|
+
project_id: str, dataset_id: str, table_id: str, policy_id: str
|
|
71
|
+
) -> dict:
|
|
72
|
+
row_access.delete(project_id, dataset_id, table_id, policy_id)
|
|
73
|
+
return {}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
from fastapi import Header, Query
|
|
2
|
+
|
|
3
|
+
from local_bigquery.api import Router, paginate, with_rows
|
|
4
|
+
from local_bigquery.catalog import tabledata, tables
|
|
5
|
+
from local_bigquery.jobs.query import translate_view
|
|
6
|
+
from local_bigquery.models import (
|
|
7
|
+
Table,
|
|
8
|
+
TableDataInsertAllRequest,
|
|
9
|
+
TableDataInsertAllResponse,
|
|
10
|
+
TableList,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
router = Router(tags=["tables"])
|
|
14
|
+
TABLE = "/projects/{project_id}/datasets/{dataset_id}/tables/{table_id}"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@router.get("/projects/{project_id}/datasets/{dataset_id}/tables")
|
|
18
|
+
def list_tables(
|
|
19
|
+
project_id: str,
|
|
20
|
+
dataset_id: str,
|
|
21
|
+
maxResults: int | None = None,
|
|
22
|
+
pageToken: str | None = None,
|
|
23
|
+
) -> TableList:
|
|
24
|
+
summaries = tables.list_(project_id, dataset_id)
|
|
25
|
+
page, token = paginate(summaries, maxResults, pageToken)
|
|
26
|
+
return TableList(
|
|
27
|
+
kind="bigquery#tableList",
|
|
28
|
+
tables=[table.dump() for table in page],
|
|
29
|
+
nextPageToken=token,
|
|
30
|
+
totalItems=len(summaries),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@router.post("/projects/{project_id}/datasets/{dataset_id}/tables")
|
|
35
|
+
def insert_table(project_id: str, dataset_id: str, body: Table) -> Table:
|
|
36
|
+
return tables.create(project_id, dataset_id, body, translate_view)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@router.get(TABLE)
|
|
40
|
+
def get_table(
|
|
41
|
+
project_id: str,
|
|
42
|
+
dataset_id: str,
|
|
43
|
+
table_id: str,
|
|
44
|
+
selectedFields: str | None = None,
|
|
45
|
+
view: str | None = None,
|
|
46
|
+
) -> Table:
|
|
47
|
+
return tables.get(project_id, dataset_id, table_id, selectedFields, view)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@router.patch(TABLE)
|
|
51
|
+
def patch_table(
|
|
52
|
+
project_id: str,
|
|
53
|
+
dataset_id: str,
|
|
54
|
+
table_id: str,
|
|
55
|
+
body: Table,
|
|
56
|
+
if_match: str | None = Header(None),
|
|
57
|
+
) -> Table:
|
|
58
|
+
return tables.update(project_id, dataset_id, table_id, body, if_match, False)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@router.put(TABLE)
|
|
62
|
+
def update_table(
|
|
63
|
+
project_id: str,
|
|
64
|
+
dataset_id: str,
|
|
65
|
+
table_id: str,
|
|
66
|
+
body: Table,
|
|
67
|
+
if_match: str | None = Header(None),
|
|
68
|
+
) -> Table:
|
|
69
|
+
return tables.update(project_id, dataset_id, table_id, body, if_match, True)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@router.delete(TABLE, status_code=204)
|
|
73
|
+
def delete_table(project_id: str, dataset_id: str, table_id: str):
|
|
74
|
+
tables.delete(project_id, dataset_id, table_id)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@router.post(f"{TABLE}/insertAll")
|
|
78
|
+
def insert_all(
|
|
79
|
+
project_id: str, dataset_id: str, table_id: str, body: TableDataInsertAllRequest
|
|
80
|
+
) -> TableDataInsertAllResponse:
|
|
81
|
+
response = tabledata.insert_all(project_id, dataset_id, table_id, body)
|
|
82
|
+
return TableDataInsertAllResponse(
|
|
83
|
+
kind="bigquery#tableDataInsertAllResponse", **response
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@router.get(f"{TABLE}/data")
|
|
88
|
+
def list_rows(
|
|
89
|
+
project_id: str,
|
|
90
|
+
dataset_id: str,
|
|
91
|
+
table_id: str,
|
|
92
|
+
maxResults: int | None = None,
|
|
93
|
+
pageToken: str | None = None,
|
|
94
|
+
startIndex: int = 0,
|
|
95
|
+
selectedFields: str | None = None,
|
|
96
|
+
int64_timestamps: bool = Query(False, alias="formatOptions.useInt64Timestamp"),
|
|
97
|
+
):
|
|
98
|
+
start = int(pageToken) if pageToken else startIndex
|
|
99
|
+
page, _ = tabledata.list_rows(
|
|
100
|
+
project_id,
|
|
101
|
+
dataset_id,
|
|
102
|
+
table_id,
|
|
103
|
+
maxResults,
|
|
104
|
+
start,
|
|
105
|
+
selectedFields,
|
|
106
|
+
int64_timestamps,
|
|
107
|
+
)
|
|
108
|
+
return with_rows(
|
|
109
|
+
{
|
|
110
|
+
"kind": "bigquery#tableDataList",
|
|
111
|
+
"totalRows": str(page.total),
|
|
112
|
+
"pageToken": page.next_token,
|
|
113
|
+
},
|
|
114
|
+
page.rows,
|
|
115
|
+
)
|