blob: c5ccf5399d49fc9263462d10b93bf41067c16ecd [file]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
from abc import ABC
from dataclasses import dataclass
from typing import Dict, List, Optional
from pypaimon.common.identifier import Identifier
from pypaimon.common.json_util import json_field
from pypaimon.function.function_change import FunctionChange
from pypaimon.function.function_definition import FunctionDefinition
from pypaimon.management.permission_access import PermissionAccess
from pypaimon.management.permission_assignment import PermissionAssignment
from pypaimon.management.permission_columns import PermissionColumns
from pypaimon.management.permission_resource import PermissionResource
from pypaimon.schema.data_types import DataField
from pypaimon.schema.schema import Schema
from pypaimon.schema.schema_change import SchemaChange
from pypaimon.snapshot.snapshot import Snapshot
from pypaimon.snapshot.snapshot_commit import PartitionStatistics
from pypaimon.table.instant import Instant
class RESTRequest(ABC):
"""RESTRequest"""
@dataclass
class CreateDatabaseRequest(RESTRequest):
FIELD_NAME = "name"
FIELD_OPTIONS = "options"
name: str = json_field(FIELD_NAME)
options: Optional[Dict[str, str]] = json_field(FIELD_OPTIONS)
@dataclass
class AlterDatabaseRequest(RESTRequest):
FIELD_REMOVALS = "removals"
FIELD_UPDATES = "updates"
removals: List[str] = json_field(FIELD_REMOVALS)
updates: Dict[str, str] = json_field(FIELD_UPDATES)
@dataclass
class RenameTableRequest(RESTRequest):
FIELD_SOURCE = "source"
FIELD_DESTINATION = "destination"
source: Identifier = json_field(FIELD_SOURCE)
destination: Identifier = json_field(FIELD_DESTINATION)
@dataclass
class CreateTableRequest(RESTRequest):
FIELD_IDENTIFIER = "identifier"
FIELD_SCHEMA = "schema"
identifier: Identifier = json_field(FIELD_IDENTIFIER)
schema: Schema = json_field(FIELD_SCHEMA)
@dataclass
class CommitTableRequest(RESTRequest):
FIELD_TABLE_ID = "tableId"
FIELD_BASE_SNAPSHOT_UUID = "baseSnapshotUuid"
FIELD_SNAPSHOT = "snapshot"
FIELD_STATISTICS = "statistics"
table_id: Optional[str] = json_field(FIELD_TABLE_ID)
snapshot: Snapshot = json_field(FIELD_SNAPSHOT)
statistics: List[PartitionStatistics] = json_field(FIELD_STATISTICS)
base_snapshot_uuid: Optional[str] = json_field(
FIELD_BASE_SNAPSHOT_UUID, default=None
)
@dataclass
class AlterTableRequest(RESTRequest):
FIELD_CHANGES = "changes"
changes: List[SchemaChange] = json_field(FIELD_CHANGES)
@dataclass
class RollbackTableRequest(RESTRequest):
FIELD_INSTANT = "instant"
FIELD_FROM_SNAPSHOT = "fromSnapshot"
instant: Instant = json_field(FIELD_INSTANT)
from_snapshot: Optional[int] = json_field(FIELD_FROM_SNAPSHOT)
@dataclass
class CreateFunctionRequest(RESTRequest):
FIELD_NAME = "name"
FIELD_INPUT_PARAMS = "inputParams"
FIELD_RETURN_PARAMS = "returnParams"
FIELD_DETERMINISTIC = "deterministic"
FIELD_DEFINITIONS = "definitions"
FIELD_COMMENT = "comment"
FIELD_OPTIONS = "options"
name: str = json_field(FIELD_NAME)
input_params: Optional[List[DataField]] = json_field(FIELD_INPUT_PARAMS, default=None)
return_params: Optional[List[DataField]] = json_field(FIELD_RETURN_PARAMS, default=None)
deterministic: bool = json_field(FIELD_DETERMINISTIC, default=False)
definitions: Optional[Dict[str, FunctionDefinition]] = json_field(FIELD_DEFINITIONS, default=None)
comment: Optional[str] = json_field(FIELD_COMMENT, default=None)
options: Optional[Dict[str, str]] = json_field(FIELD_OPTIONS, default=None)
def to_dict(self) -> Dict:
result = {
self.FIELD_NAME: self.name,
self.FIELD_DETERMINISTIC: self.deterministic,
}
if self.input_params is not None:
result[self.FIELD_INPUT_PARAMS] = [
p.to_dict() if hasattr(p, 'to_dict') else p for p in self.input_params
]
else:
result[self.FIELD_INPUT_PARAMS] = None
if self.return_params is not None:
result[self.FIELD_RETURN_PARAMS] = [
p.to_dict() if hasattr(p, 'to_dict') else p for p in self.return_params
]
else:
result[self.FIELD_RETURN_PARAMS] = None
if self.definitions is not None:
result[self.FIELD_DEFINITIONS] = {
k: v.to_dict() if hasattr(v, 'to_dict') else v
for k, v in self.definitions.items()
}
else:
result[self.FIELD_DEFINITIONS] = None
result[self.FIELD_COMMENT] = self.comment
result[self.FIELD_OPTIONS] = self.options
return result
@dataclass
class AlterFunctionRequest(RESTRequest):
FIELD_CHANGES = "changes"
changes: List[FunctionChange] = json_field(FIELD_CHANGES)
def to_dict(self) -> Dict:
return {
self.FIELD_CHANGES: [c.to_dict() for c in self.changes]
}
# Wire DTO for ``POST /databases/{db}/tables/{tbl}/tags``. Mirrors Java
# ``CreateTagRequest`` (paimon-api/.../rest/requests/CreateTagRequest.java) — only
# three fields are serialized. ``ignoreIfExists`` is intentionally NOT included
# here; it is a client-side flag handled by ``RESTCatalog.create_tag``, not part
# of the wire format.
@dataclass
class CreateTagRequest(RESTRequest):
FIELD_TAG_NAME = "tagName"
FIELD_SNAPSHOT_ID = "snapshotId"
FIELD_TIME_RETAINED = "timeRetained"
tag_name: str = json_field(FIELD_TAG_NAME)
snapshot_id: Optional[int] = json_field(FIELD_SNAPSHOT_ID, default=None)
time_retained: Optional[str] = json_field(FIELD_TIME_RETAINED, default=None)
@dataclass
class CreatePartitionsRequest(RESTRequest):
FIELD_PARTITION_SPECS = "partitionSpecs"
FIELD_IGNORE_IF_EXISTS = "ignoreIfExists"
partition_specs: List[Dict[str, str]] = json_field(FIELD_PARTITION_SPECS)
ignore_if_exists: Optional[bool] = json_field(FIELD_IGNORE_IF_EXISTS, default=True)
def __post_init__(self):
if self.ignore_if_exists is None:
self.ignore_if_exists = True
# Branch CRUD wire DTOs. Mirrors Java requests in
# paimon-api/.../rest/requests/.
@dataclass
class CreateBranchRequest(RESTRequest):
FIELD_BRANCH = "branch"
FIELD_FROM_TAG = "fromTag"
branch: str = json_field(FIELD_BRANCH)
from_tag: Optional[str] = json_field(FIELD_FROM_TAG, default=None)
@dataclass
class RenameBranchRequest(RESTRequest):
FIELD_TO_BRANCH = "toBranch"
to_branch: str = json_field(FIELD_TO_BRANCH)
@dataclass
class ForwardBranchRequest(RESTRequest):
"""Empty body request; serializes to ``{}`` per Java ForwardBranchRequest."""
pass
class GrantPermissionRequest(RESTRequest):
def __init__(self, assignment: PermissionAssignment):
self._assignment = assignment
def assignment(self) -> PermissionAssignment:
return self._assignment
def get_resource(self) -> PermissionResource:
return self._assignment.get_resource()
def get_access(self) -> str:
return self._assignment.get_access()
def get_principal(self) -> str:
return self._assignment.get_principal()
def get_columns(self) -> Optional[PermissionColumns]:
return self._assignment.get_columns()
def get_expire_time(self) -> Optional[str]:
return self._assignment.get_expire_time()
def to_dict(self) -> dict:
return self._assignment.to_dict()
@classmethod
def from_dict(cls, data: dict) -> "GrantPermissionRequest":
resource = data.get(PermissionAssignment.FIELD_RESOURCE)
columns = data.get(PermissionAssignment.FIELD_COLUMNS)
return cls(PermissionAssignment(
None if resource is None else PermissionResource.from_dict(resource),
data.get(PermissionAssignment.FIELD_ACCESS),
data.get(PermissionAssignment.FIELD_PRINCIPAL),
None if columns is None else PermissionColumns.from_dict(columns),
data.get(PermissionAssignment.FIELD_EXPIRE_TIME)))
class RevokePermissionRequest(RESTRequest):
FIELD_RESOURCE = "resource"
FIELD_ACCESS = "access"
FIELD_PRINCIPAL = "principal"
def __init__(self, resource: PermissionResource, access: str, principal: str):
self._resource = resource
self._access = PermissionAccess.canonicalize_for(resource, access)
self._principal = PermissionAssignment.validate_principal(principal)
def get_resource(self) -> PermissionResource:
return self._resource
def get_access(self) -> str:
return self._access
def get_principal(self) -> str:
return self._principal
def to_dict(self) -> dict:
return {
self.FIELD_RESOURCE: self._resource.to_dict(),
self.FIELD_ACCESS: self._access,
self.FIELD_PRINCIPAL: self._principal,
}
@classmethod
def from_dict(cls, data: dict) -> "RevokePermissionRequest":
resource = data.get(cls.FIELD_RESOURCE)
return cls(None if resource is None else PermissionResource.from_dict(resource),
data.get(cls.FIELD_ACCESS), data.get(cls.FIELD_PRINCIPAL))