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.
Files changed (94) hide show
  1. local_bigquery/__init__.py +3 -0
  2. local_bigquery/api/__init__.py +30 -0
  3. local_bigquery/api/datasets.py +59 -0
  4. local_bigquery/api/iam.py +29 -0
  5. local_bigquery/api/jobs.py +176 -0
  6. local_bigquery/api/models.py +40 -0
  7. local_bigquery/api/projects.py +35 -0
  8. local_bigquery/api/routines.py +81 -0
  9. local_bigquery/api/row_access_policies.py +73 -0
  10. local_bigquery/api/tables.py +115 -0
  11. local_bigquery/api/uploads.py +80 -0
  12. local_bigquery/app.py +145 -0
  13. local_bigquery/catalog/__init__.py +0 -0
  14. local_bigquery/catalog/datasets.py +119 -0
  15. local_bigquery/catalog/ddl.py +215 -0
  16. local_bigquery/catalog/iam.py +68 -0
  17. local_bigquery/catalog/indexes.py +59 -0
  18. local_bigquery/catalog/metadata.py +112 -0
  19. local_bigquery/catalog/models.py +102 -0
  20. local_bigquery/catalog/names.py +83 -0
  21. local_bigquery/catalog/options.py +102 -0
  22. local_bigquery/catalog/routines.py +144 -0
  23. local_bigquery/catalog/row_access.py +174 -0
  24. local_bigquery/catalog/tabledata.py +203 -0
  25. local_bigquery/catalog/tables.py +430 -0
  26. local_bigquery/cli.py +42 -0
  27. local_bigquery/discovery.json +11957 -0
  28. local_bigquery/engine/__init__.py +0 -0
  29. local_bigquery/engine/database.py +159 -0
  30. local_bigquery/engine/results.py +102 -0
  31. local_bigquery/engine/sessions.py +40 -0
  32. local_bigquery/engine/types.py +222 -0
  33. local_bigquery/errors.py +287 -0
  34. local_bigquery/grpc/__init__.py +0 -0
  35. local_bigquery/grpc/read.py +258 -0
  36. local_bigquery/grpc/server.py +121 -0
  37. local_bigquery/grpc/write.py +301 -0
  38. local_bigquery/jobs/__init__.py +0 -0
  39. local_bigquery/jobs/copy.py +46 -0
  40. local_bigquery/jobs/extract.py +95 -0
  41. local_bigquery/jobs/load.py +281 -0
  42. local_bigquery/jobs/merge.py +139 -0
  43. local_bigquery/jobs/query.py +428 -0
  44. local_bigquery/jobs/runner.py +243 -0
  45. local_bigquery/jobs/storage.py +81 -0
  46. local_bigquery/jobs/store.py +61 -0
  47. local_bigquery/models.py +2104 -0
  48. local_bigquery/pytest_plugin.py +59 -0
  49. local_bigquery/repl.py +125 -0
  50. local_bigquery/resource.py +41 -0
  51. local_bigquery/settings.py +23 -0
  52. local_bigquery/sql/__init__.py +0 -0
  53. local_bigquery/sql/dialect.py +241 -0
  54. local_bigquery/sql/functions/aggregates.sql +24 -0
  55. local_bigquery/sql/functions/arrays.sql +23 -0
  56. local_bigquery/sql/functions/conditional.sql +1 -0
  57. local_bigquery/sql/functions/datetime.sql +68 -0
  58. local_bigquery/sql/functions/geography.sql +126 -0
  59. local_bigquery/sql/functions/json.sql +145 -0
  60. local_bigquery/sql/functions/math.sql +34 -0
  61. local_bigquery/sql/functions/net.sql +59 -0
  62. local_bigquery/sql/functions/ranges.sql +49 -0
  63. local_bigquery/sql/functions/strings.sql +81 -0
  64. local_bigquery/sql/js.py +160 -0
  65. local_bigquery/sql/native.py +345 -0
  66. local_bigquery/sql/params.py +118 -0
  67. local_bigquery/sql/rules/__init__.py +49 -0
  68. local_bigquery/sql/rules/arrays.py +122 -0
  69. local_bigquery/sql/rules/columns.py +24 -0
  70. local_bigquery/sql/rules/datetime.py +419 -0
  71. local_bigquery/sql/rules/ddl.py +161 -0
  72. local_bigquery/sql/rules/dml.py +42 -0
  73. local_bigquery/sql/rules/external.py +46 -0
  74. local_bigquery/sql/rules/information_schema.py +604 -0
  75. local_bigquery/sql/rules/json.py +54 -0
  76. local_bigquery/sql/rules/literals.py +51 -0
  77. local_bigquery/sql/rules/math.py +91 -0
  78. local_bigquery/sql/rules/parameters.py +70 -0
  79. local_bigquery/sql/rules/partitions.py +83 -0
  80. local_bigquery/sql/rules/query.py +140 -0
  81. local_bigquery/sql/rules/ranges.py +114 -0
  82. local_bigquery/sql/rules/row_access.py +71 -0
  83. local_bigquery/sql/rules/safe.py +42 -0
  84. local_bigquery/sql/rules/strings.py +148 -0
  85. local_bigquery/sql/rules/tables.py +221 -0
  86. local_bigquery/sql/rules/typing.py +319 -0
  87. local_bigquery/sql/rules/udfs.py +72 -0
  88. local_bigquery/sql/script.py +672 -0
  89. local_bigquery/sql/translate.py +62 -0
  90. local_bigquery-0.3.0.dist-info/METADATA +168 -0
  91. local_bigquery-0.3.0.dist-info/RECORD +94 -0
  92. local_bigquery-0.3.0.dist-info/WHEEL +4 -0
  93. local_bigquery-0.3.0.dist-info/entry_points.txt +5 -0
  94. local_bigquery-0.3.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,3 @@
1
+ from local_bigquery.app import app
2
+
3
+ __all__ = ["app"]
@@ -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
+ )