This is an automated email from the ASF dual-hosted git repository.
honahx pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/polaris.git
The following commit(s) were added to refs/heads/main by this push:
new 3b06a8034 Update Makefile for python client with auto setup (#1995)
3b06a8034 is described below
commit 3b06a803458aa61ad691d2edd132157dafbfb2d3
Author: Yong Zheng <[email protected]>
AuthorDate: Thu Jul 10 15:44:45 2025 -0500
Update Makefile for python client with auto setup (#1995)
Automate python client setup and use a virtual env instead to avoid change
an end-users' OS python
---
.github/workflows/python-client.yml | 18 +--
client/python/.pre-commit-config.yaml | 7 +-
client/python/Makefile | 82 +++++++++++++-
client/python/README.md | 14 +--
client/python/cli/constants.py | 205 +++++++++++++++++++---------------
client/python/cli/polaris_cli.py | 8 +-
client/python/pyproject.toml | 2 +-
7 files changed, 212 insertions(+), 124 deletions(-)
diff --git a/.github/workflows/python-client.yml
b/.github/workflows/python-client.yml
index 37ead61ed..7586e3b18 100644
--- a/.github/workflows/python-client.yml
+++ b/.github/workflows/python-client.yml
@@ -58,15 +58,6 @@ jobs:
with:
python-version: ${{ matrix.python-version }}
- - name: Install Poetry
- run: |
- pip install --user --upgrade -r regtests/requirements.txt
-
- # TODO: add cache for poetry dependencies once we have poetry.lock in
the repo
- - name: Install dependencies
- working-directory: client/python
- run: poetry install --all-extras
-
- name: Lint
working-directory: client/python
run: |
@@ -75,15 +66,14 @@ jobs:
- name: Generated Client Tests
working-directory: client/python
run: |
- export SCRIPT_DIR="non-existing-mock-directory"
- poetry run pytest test/
+ make test-client
- name: Image build
run: |
./gradlew \
- :polaris-server:assemble \
- :polaris-server:quarkusAppPartsBuild --rerun \
- -Dquarkus.container-image.build=true
+ :polaris-server:assemble \
+ :polaris-server:quarkusAppPartsBuild --rerun \
+ -Dquarkus.container-image.build=true
- name: Integration Tests
working-directory: client/python
diff --git a/client/python/.pre-commit-config.yaml
b/client/python/.pre-commit-config.yaml
index 84b1ab95e..5ffb2bfc6 100644
--- a/client/python/.pre-commit-config.yaml
+++ b/client/python/.pre-commit-config.yaml
@@ -23,10 +23,12 @@ repos:
- id: end-of-file-fixer
- id: debug-statements
- repo: https://github.com/astral-sh/ruff-pre-commit
- rev: v0.11.13
+ rev: v0.12.1
hooks:
- - id: ruff
+ # Run the linter.
+ - id: ruff-check
args: [ --fix, --exit-non-zero-on-fix ]
+ # Run the formatter.
- id: ruff-format
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.16.0
@@ -34,3 +36,4 @@ repos:
- id: mypy
args:
[--disallow-untyped-defs, --ignore-missing-imports, --install-types,
--non-interactive]
+ files: 'integration_tests/.*\.py'
diff --git a/client/python/Makefile b/client/python/Makefile
index 7d65922ab..9d222fa39 100644
--- a/client/python/Makefile
+++ b/client/python/Makefile
@@ -15,10 +15,65 @@
# specific language governing permissions and limitations
# under the License.
-regenerate-client:
+# .SILENT:
+
+# Configures the shell for recipes to use bash, enabling bash commands and
ensuring
+# that recipes exit on any command failure (including within pipes).
+SHELL = /usr/bin/env bash -o pipefail
+.SHELLFLAGS = -ec
+
+# Version information
+VERSION ?= $(shell cat pyproject.toml | grep version | sed 's/version *=
*"\(.*\)"/\1/')
+BUILD_DATE := $(shell date -u +"%Y-%m-%dT%H:%M:%S%:z")
+GIT_COMMIT := $(shell git rev-parse HEAD)
+POETRY_VERSION := $(shell cat pyproject.toml | grep requires-poetry | sed
's/requires-poetry *= *"\(.*\)"/\1/')
+
+# Variables
+VENV_DIR := .venv
+
+.PHONY: help
+help: ## Display this help.
+ @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make
\033[36m<target>\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf "
\033[36m%-30s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n",
substr($$0, 5) } ' $(MAKEFILE_LIST)
+
+.PHONY: version
+version: ## Print version information.
+ @echo "Apache Polaris version: ${VERSION}"
+ @echo "Build date: ${BUILD_DATE}"
+ @echo "Git commit: ${GIT_COMMIT}"
+ @echo "Poetry version: ${POETRY_VERSION}"
+
+# Target to create the virtual environment directory
+$(VENV_DIR):
+ @echo "Setting up Python virtual environment at $(VENV_DIR)..."
+ python3 -m venv $(VENV_DIR)
+ @echo "Virtual environment created."
+
+.PHONY: setup-env
+setup-env: $(VENV_DIR) install-poetry-deps
+
+.PHONY: install-poetry-deps
+install-poetry-deps:
+ @echo "Installing Poetry and project dependencies into $(VENV_DIR)..."
+ # Ensure pip is up-to-date within the venv
+ $(VENV_DIR)/bin/pip install --upgrade pip
+ # Install poetry if not already present
+ @if [ ! -f "$(VENV_DIR)/bin/poetry" ]; then \
+ $(VENV_DIR)/bin/pip install --upgrade
"poetry${POETRY_VERSION}"; \
+ fi
+ # Install needed dependencies using poetry
+ $(VENV_DIR)/bin/poetry install --all-extras
+ @echo "Poetry and dependencies installed."
+
+.PHONY: regenerate-client
+regenerate-client: ## Regenerate the client code
../templates/regenerate.sh
-test-integration:
+.PHONY: test-client
+test-client: setup-env ## Run client tests
+ SCRIPT_DIR="non-existing-mock-directory" $(VENV_DIR)/bin/poetry run
pytest test/
+
+.PHONY: test-integration
+test-integration: setup-env ## Run integration tests
docker compose -f docker-compose.yml kill
docker compose -f docker-compose.yml rm -f
docker compose -f docker-compose.yml up -d
@@ -28,8 +83,25 @@ test-integration:
echo "Still waiting for HTTP 200 from /q/health..."; \
done
@echo "Polaris is healthy. Starting integration tests..."
- poetry run pytest integration_tests/ ${PYTEST_ARGS}
+ $(VENV_DIR)/bin/poetry run pytest integration_tests/ ${PYTEST_ARGS}
+
+.PHONY: lint
+lint: setup-env ## Run linting checks
+ $(VENV_DIR)/bin/poetry run pre-commit run --files integration_tests/*
cli/*
+.PHONY: clean-venv
+clean-venv:
+ @echo "Attempting to remove virtual environment directory:
$(VENV_DIR)..."
+ # SAFETY CHECK: Ensure VENV_DIR is not empty and exists before
attempting to remove
+ @if [ -n "$(VENV_DIR)" ] && [ -d "$(VENV_DIR)" ]; then \
+ rm -rf "$(VENV_DIR)"; \
+ echo "Virtual environment removed."; \
+ else \
+ echo "Virtual environment directory '$(VENV_DIR)' not found or
VENV_DIR is empty. No action taken."; \
+ fi
-lint:
- poetry run pre-commit run --files integration_tests/*
+.PHONY: clean
+clean: clean-venv ## Cleanup
+ @echo "Cleaning up Python cache files..."
+ find . -type f -name "*.pyc" -delete
+ find . -type d -name "__pycache__" -delete
diff --git a/client/python/README.md b/client/python/README.md
index cec952681..4489452a1 100644
--- a/client/python/README.md
+++ b/client/python/README.md
@@ -6,9 +6,9 @@
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
@@ -24,17 +24,13 @@ The Apache Polaris Python package provides a client for
interacting with the Apa
### Prerequisites
- Python 3.9 or later
-- poetry >= 2.0
+- poetry >= 2.1
### Installation
-First we need to generate the OpenAPI client code from the OpenAPI
specification.
+First we need to generate the OpenAPI client code from the OpenAPI
specification.
```
make regenerate-client
```
-Install the project with test dependencies:
-```
-poetry install --all-extras
-```
### Auto-formatting and Linting
```
@@ -44,4 +40,4 @@ make lint
### Running Integration Tests
```
make test-integration
-```
\ No newline at end of file
+```
diff --git a/client/python/cli/constants.py b/client/python/cli/constants.py
index f82ff2caa..93b36d998 100644
--- a/client/python/cli/constants.py
+++ b/client/python/cli/constants.py
@@ -53,8 +53,8 @@ class CatalogConnectionType(Enum):
Represents a ConnectionType for an EXTERNAL catalog -- see
ConnectionConfigInfo in the spec
"""
- HADOOP = 'hadoop'
- ICEBERG = 'iceberg-rest'
+ HADOOP = "hadoop"
+ ICEBERG = "iceberg-rest"
class AuthenticationType(Enum):
@@ -62,9 +62,9 @@ class AuthenticationType(Enum):
Represents a AuthenticationType for an EXTERNAL catalog -- see
AuthenticationParameters in the spec
"""
- OAUTH = 'oauth'
- BEARER = 'bearer'
- SIGV4 = 'sigv4'
+ OAUTH = "oauth"
+ BEARER = "bearer"
+ SIGV4 = "sigv4"
class ServiceIdentityType(Enum):
@@ -72,7 +72,7 @@ class ServiceIdentityType(Enum):
Represents a Service Identity Type for an EXTERNAL catalog -- see
ServiceIdentityInfo in the spec
"""
- AWS_IAM = 'aws_iam'
+ AWS_IAM = "aws_iam"
class Commands:
@@ -129,57 +129,57 @@ class Arguments:
These values should be snake_case, but they will get mapped to kebab-case
in `Parser.parse`
"""
- TYPE = 'type'
- DEFAULT_BASE_LOCATION = 'default_base_location'
- STORAGE_TYPE = 'storage_type'
- ALLOWED_LOCATION = 'allowed_location'
- ROLE_ARN = 'role_arn'
- EXTERNAL_ID = 'external_id'
- USER_ARN = 'user_arn'
- TENANT_ID = 'tenant_id'
- MULTI_TENANT_APP_NAME = 'multi_tenant_app_name'
- CONSENT_URL = 'consent_url'
- SERVICE_ACCOUNT = 'service_account'
- CATALOG_ROLE = 'catalog_role'
- CATALOG = 'catalog'
- PRINCIPAL = 'principal'
- CLIENT_ID = 'client_id'
- PRINCIPAL_ROLE = 'principal_role'
- PROPERTY = 'property'
- SET_PROPERTY = 'set_property'
- REMOVE_PROPERTY = 'remove_property'
- PRIVILEGE = 'privilege'
- NAMESPACE = 'namespace'
- TABLE = 'table'
- VIEW = 'view'
- CASCADE = 'cascade'
- CLIENT_SECRET = 'client_secret'
- ACCESS_TOKEN = 'access_token'
- HOST = 'host'
- PORT = 'port'
- BASE_URL = 'base_url'
- PARENT = 'parent'
- LOCATION = 'location'
- REGION = 'region'
- PROFILE = 'profile'
- PROXY = 'proxy'
- HADOOP_WAREHOUSE = 'hadoop_warehouse'
- ICEBERG_REMOTE_CATALOG_NAME = 'iceberg_remote_catalog_name'
- CATALOG_CONNECTION_TYPE = 'catalog_connection_type'
- CATALOG_AUTHENTICATION_TYPE = 'catalog_authentication_type'
- CATALOG_SERVICE_IDENTITY_TYPE = 'catalog_service_identity_type'
- CATALOG_SERVICE_IDENTITY_IAM_ARN = 'catalog_service_identity_iam_arn'
- CATALOG_URI = 'catalog_uri'
- CATALOG_TOKEN_URI = 'catalog_token_uri'
- CATALOG_CLIENT_ID = 'catalog_client_id'
- CATALOG_CLIENT_SECRET = 'catalog_client_secret'
- CATALOG_CLIENT_SCOPE = 'catalog_client_scope'
- CATALOG_BEARER_TOKEN = 'catalog_bearer_token'
- CATALOG_ROLE_ARN = 'catalog_role_arn'
- CATALOG_ROLE_SESSION_NAME = 'catalog_role_session_name'
- CATALOG_EXTERNAL_ID = 'catalog_external_id'
- CATALOG_SIGNING_REGION = 'catalog_signing_region'
- CATALOG_SIGNING_NAME = 'catalog_signing_name'
+ TYPE = "type"
+ DEFAULT_BASE_LOCATION = "default_base_location"
+ STORAGE_TYPE = "storage_type"
+ ALLOWED_LOCATION = "allowed_location"
+ ROLE_ARN = "role_arn"
+ EXTERNAL_ID = "external_id"
+ USER_ARN = "user_arn"
+ TENANT_ID = "tenant_id"
+ MULTI_TENANT_APP_NAME = "multi_tenant_app_name"
+ CONSENT_URL = "consent_url"
+ SERVICE_ACCOUNT = "service_account"
+ CATALOG_ROLE = "catalog_role"
+ CATALOG = "catalog"
+ PRINCIPAL = "principal"
+ CLIENT_ID = "client_id"
+ PRINCIPAL_ROLE = "principal_role"
+ PROPERTY = "property"
+ SET_PROPERTY = "set_property"
+ REMOVE_PROPERTY = "remove_property"
+ PRIVILEGE = "privilege"
+ NAMESPACE = "namespace"
+ TABLE = "table"
+ VIEW = "view"
+ CASCADE = "cascade"
+ CLIENT_SECRET = "client_secret"
+ ACCESS_TOKEN = "access_token"
+ HOST = "host"
+ PORT = "port"
+ BASE_URL = "base_url"
+ PARENT = "parent"
+ LOCATION = "location"
+ REGION = "region"
+ PROFILE = "profile"
+ PROXY = "proxy"
+ HADOOP_WAREHOUSE = "hadoop_warehouse"
+ ICEBERG_REMOTE_CATALOG_NAME = "iceberg_remote_catalog_name"
+ CATALOG_CONNECTION_TYPE = "catalog_connection_type"
+ CATALOG_AUTHENTICATION_TYPE = "catalog_authentication_type"
+ CATALOG_SERVICE_IDENTITY_TYPE = "catalog_service_identity_type"
+ CATALOG_SERVICE_IDENTITY_IAM_ARN = "catalog_service_identity_iam_arn"
+ CATALOG_URI = "catalog_uri"
+ CATALOG_TOKEN_URI = "catalog_token_uri"
+ CATALOG_CLIENT_ID = "catalog_client_id"
+ CATALOG_CLIENT_SECRET = "catalog_client_secret"
+ CATALOG_CLIENT_SCOPE = "catalog_client_scope"
+ CATALOG_BEARER_TOKEN = "catalog_bearer_token"
+ CATALOG_ROLE_ARN = "catalog_role_arn"
+ CATALOG_ROLE_SESSION_NAME = "catalog_role_session_name"
+ CATALOG_EXTERNAL_ID = "catalog_external_id"
+ CATALOG_SIGNING_REGION = "catalog_signing_region"
+ CATALOG_SIGNING_NAME = "catalog_signing_name"
class Hints:
@@ -237,39 +237,64 @@ class Hints:
DEFAULT_BASE_LOCATION = "A new default base location for the
catalog"
class External:
- CATALOG_CONNECTION_TYPE = 'The type of external catalog in
[ICEBERG, HADOOP].'
- CATALOG_AUTHENTICATION_TYPE = 'The type of authentication in
[OAUTH, BEARER, SIGV4]'
- CATALOG_SERVICE_IDENTITY_TYPE = 'The type of service identity in
[AWS_IAM]'
-
- CATALOG_SERVICE_IDENTITY_IAM_ARN = ('When using the AWS_IAM
service identity type, this is the ARN '
- 'of the IAM user or IAM role
Polaris uses to assume roles and '
- 'then access external
resources.')
-
- CATALOG_URI = 'The URI of the external catalog'
- HADOOP_WAREHOUSE = 'The warehouse to use when federating to a
HADOOP catalog'
- ICEBERG_REMOTE_CATALOG_NAME = 'The remote catalog name when
federating to an Iceberg REST catalog'
-
-
- CATALOG_TOKEN_URI = '(For authentication type OAUTH) Token server
URI'
- CATALOG_CLIENT_ID = '(For authentication type OAUTH) oauth client
id'
- CATALOG_CLIENT_SECRET = '(For authentication type OAUTH) oauth
client secret (input-only)'
- CATALOG_CLIENT_SCOPE = ('(For authentication type OAUTH) oauth
scopes to specify when exchanging '
- 'for a short-lived access token. Multiple
can be provided by specifying'
- ' this option more than once')
-
- CATALOG_BEARER_TOKEN = '(For authentication type BEARER) Bearer
token (input-only)'
-
- CATALOG_ROLE_ARN = ('(For authentication type SIGV4) The aws IAM
role arn assumed by polaris '
- 'userArn when signing requests')
- CATALOG_ROLE_SESSION_NAME = ('(For authentication type SIGV4) The
role session name to be used '
- 'by the SigV4 protocol for signing
requests')
- CATALOG_EXTERNAL_ID = ('(For authentication type SIGV4) An
optional external id used to establish '
- 'a trust relationship with AWS in the trust
policy')
- CATALOG_SIGNING_REGION = ('(For authentication type SIGV4) Region
to be used by the SigV4 protocol '
- 'for signing requests')
- CATALOG_SIGNING_NAME = ('(For authentication type SIGV4) The
service name to be used by the SigV4 '
- 'protocol for signing requests, the
default signing name is "execute-api" '
- 'is if not provided')
+ CATALOG_CONNECTION_TYPE = (
+ "The type of external catalog in [ICEBERG, HADOOP]."
+ )
+ CATALOG_AUTHENTICATION_TYPE = (
+ "The type of authentication in [OAUTH, BEARER, SIGV4]"
+ )
+ CATALOG_SERVICE_IDENTITY_TYPE = "The type of service identity in
[AWS_IAM]"
+
+ CATALOG_SERVICE_IDENTITY_IAM_ARN = (
+ "When using the AWS_IAM service identity type, this is the ARN
"
+ "of the IAM user or IAM role Polaris uses to assume roles and "
+ "then access external resources."
+ )
+
+ CATALOG_URI = "The URI of the external catalog"
+ HADOOP_WAREHOUSE = (
+ "The warehouse to use when federating to a HADOOP catalog"
+ )
+ ICEBERG_REMOTE_CATALOG_NAME = (
+ "The remote catalog name when federating to an Iceberg REST
catalog"
+ )
+
+ CATALOG_TOKEN_URI = "(For authentication type OAUTH) Token server
URI"
+ CATALOG_CLIENT_ID = "(For authentication type OAUTH) oauth client
id"
+ CATALOG_CLIENT_SECRET = (
+ "(For authentication type OAUTH) oauth client secret
(input-only)"
+ )
+ CATALOG_CLIENT_SCOPE = (
+ "(For authentication type OAUTH) oauth scopes to specify when
exchanging "
+ "for a short-lived access token. Multiple can be provided by
specifying"
+ " this option more than once"
+ )
+
+ CATALOG_BEARER_TOKEN = (
+ "(For authentication type BEARER) Bearer token (input-only)"
+ )
+
+ CATALOG_ROLE_ARN = (
+ "(For authentication type SIGV4) The aws IAM role arn assumed
by polaris "
+ "userArn when signing requests"
+ )
+ CATALOG_ROLE_SESSION_NAME = (
+ "(For authentication type SIGV4) The role session name to be
used "
+ "by the SigV4 protocol for signing requests"
+ )
+ CATALOG_EXTERNAL_ID = (
+ "(For authentication type SIGV4) An optional external id used
to establish "
+ "a trust relationship with AWS in the trust policy"
+ )
+ CATALOG_SIGNING_REGION = (
+ "(For authentication type SIGV4) Region to be used by the
SigV4 protocol "
+ "for signing requests"
+ )
+ CATALOG_SIGNING_NAME = (
+ "(For authentication type SIGV4) The service name to be used
by the SigV4 "
+ 'protocol for signing requests, the default signing name is
"execute-api" '
+ "is if not provided"
+ )
class Principals:
class Create:
diff --git a/client/python/cli/polaris_cli.py b/client/python/cli/polaris_cli.py
index 47e803865..83341ada4 100644
--- a/client/python/cli/polaris_cli.py
+++ b/client/python/cli/polaris_cli.py
@@ -158,9 +158,11 @@ class PolarisCli:
# Authenticate accordingly
if options.base_url:
if options.host is not None or options.port is not None:
- raise Exception(f'Please provide either
{Argument.to_flag_name(Arguments.BASE_URL)} or'
- f' {Argument.to_flag_name(Arguments.HOST)} &'
- f' {Argument.to_flag_name(Arguments.PORT)},
but not both')
+ raise Exception(
+ f"Please provide either
{Argument.to_flag_name(Arguments.BASE_URL)} or"
+ f" {Argument.to_flag_name(Arguments.HOST)} &"
+ f" {Argument.to_flag_name(Arguments.PORT)}, but not both"
+ )
polaris_management_url = f"{options.base_url}/api/management/v1"
polaris_catalog_url = f"{options.base_url}/api/catalog/v1"
diff --git a/client/python/pyproject.toml b/client/python/pyproject.toml
index a5e20d97f..9740437bf 100644
--- a/client/python/pyproject.toml
+++ b/client/python/pyproject.toml
@@ -42,7 +42,7 @@ homepage = "https://polaris.apache.org/"
repository = "https://github.com/apache/polaris/"
[tool.poetry]
-requires-poetry = ">=2.1"
+requires-poetry = "==2.1.3"
packages = [{ include = "polaris" }]
[tool.poetry.group.test.dependencies]