diff --git a/CHANGELOG.md b/CHANGELOG.md index 493e0c60db..4e377af05b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,7 @@ request adding CHANGELOG notes for breaking (!) changes and possibly other secti - `client.region` is no longer considered a "credential" property (related to Iceberg REST Catalog API). - Relaxed the requirements for S3 storage's ARN to allow Polaris to connect to more non-AWS S3 storage appliances. - Added checksum to helm deployment so that it will restart when the configmap has changed. +- Added Windows support for Python client ### Deprecations diff --git a/client/python/generate_clients.py b/client/python/generate_clients.py index 0c7c1b5af0..c8f42cffe5 100644 --- a/client/python/generate_clients.py +++ b/client/python/generate_clients.py @@ -36,12 +36,12 @@ # Paths CLIENT_DIR = Path(__file__).parent -HEADER_DIR = os.path.join(CLIENT_DIR, "templates") -SPEC_DIR = os.path.join(CLIENT_DIR, "spec") -POLARIS_MANAGEMENT_SPEC = os.path.join(SPEC_DIR, "polaris-management-service.yml") -ICEBERG_CATALOG_SPEC = os.path.join(SPEC_DIR, "iceberg-rest-catalog-open-api.yaml") -POLARIS_CATALOG_SPEC = os.path.join(SPEC_DIR, "polaris-catalog-service.yaml") -OPEN_API_GENERATOR_IGNORE = os.path.join(CLIENT_DIR, ".openapi-generator-ignore") +HEADER_DIR = CLIENT_DIR / "templates" +SPEC_DIR = CLIENT_DIR / "spec" +POLARIS_MANAGEMENT_SPEC = SPEC_DIR / "polaris-management-service.yml" +ICEBERG_CATALOG_SPEC = SPEC_DIR / "iceberg-rest-catalog-open-api.yaml" +POLARIS_CATALOG_SPEC = SPEC_DIR / "polaris-catalog-service.yaml" +OPEN_API_GENERATOR_IGNORE = CLIENT_DIR / ".openapi-generator-ignore" # Open API Generator Configs PACKAGE_NAME_POLARIS_MANAGEMENT = ( @@ -58,20 +58,20 @@ ] EXCLUDE_PATHS = [ Path(".gitignore"), - Path(".openapi-generator/"), + Path(".openapi-generator"), Path(".openapi-generator-ignore"), - Path(".pytest_cache/"), + Path(".pytest_cache"), Path("test/test_cli_parsing.py"), - Path("apache_polaris/__pycache__/"), - Path("apache_polaris/cli/"), - Path("apache_polaris/sdk/__pycache__/"), - Path("apache_polaris/sdk/catalog/__pycache__/"), - Path("apache_polaris/sdk/catalog/models/__pycache__/"), - Path("apache_polaris/sdk/catalog/api/__pycache__/"), - Path("apache_polaris/sdk/management/__pycache__/"), - Path("apache_polaris/sdk/management/models/__pycache__/"), - Path("apache_polaris/sdk/management/api/__pycache__/"), - Path("integration_tests/"), + Path("apache_polaris/__pycache__"), + Path("apache_polaris/cli"), + Path("apache_polaris/sdk/__pycache__"), + Path("apache_polaris/sdk/catalog/__pycache__"), + Path("apache_polaris/sdk/catalog/models/__pycache__"), + Path("apache_polaris/sdk/catalog/api/__pycache__"), + Path("apache_polaris/sdk/management/__pycache__"), + Path("apache_polaris/sdk/management/models/__pycache__"), + Path("apache_polaris/sdk/management/api/__pycache__"), + Path("integration_tests"), Path(".github/workflows/python.yml"), Path(".gitlab-ci.yml"), Path("pyproject.toml"), @@ -86,9 +86,9 @@ Path("README.md"), Path("generate_clients.py"), Path(".venv"), - Path("dist/"), - Path("templates/"), - Path("spec/"), + Path("dist"), + Path("templates"), + Path("spec"), Path("PKG-INFO"), ] EXCLUDE_EXTENSIONS = [ @@ -223,64 +223,56 @@ def _prepend_header_to_file(file_path: Path, header_file_path: Path) -> None: def prepend_licenses() -> None: logger.info("Re-applying license headers...") + + # Combine all paths to exclude into one set. + all_excluded_paths = set(EXCLUDE_PATHS) | set(KEEP_TEST_FILES) + for file_path in CLIENT_DIR.rglob("*"): - if file_path.is_file(): - relative_file_path = file_path.relative_to(CLIENT_DIR) - file_extension = "" - # If it's a "dotfile" like .keep - if ( - relative_file_path.name.startswith(".") - and "." not in relative_file_path.name[1:] - ): - # e.g., for '.keep', this is 'keep' - file_extension = relative_file_path.name.lstrip(".") - else: - # For standard files like generate_clients.py - file_extension = file_path.suffix.lstrip(".") - - # Check if extension is excluded - if file_extension in EXCLUDE_EXTENSIONS: - logger.debug(f"{relative_file_path}: skipped (extension excluded)") - continue - - is_excluded = False - # Combine EXCLUDE_PATHS and KEEP_TEST_FILES for comprehensive exclusion check - # Convert Path objects in EXCLUDE_PATHS to strings for fnmatch compatibility - # Ensure patterns ending with '/' are handled for directory matching - all_exclude_patterns = [ - str(p) + ("/" if p.is_dir() else "") for p in EXCLUDE_PATHS - ] + [str(p) for p in KEEP_TEST_FILES] - - for exclude_pattern_str in all_exclude_patterns: - # Handle direct file match or if the file is within an excluded directory - if fnmatch.fnmatch(str(relative_file_path), exclude_pattern_str) or ( - exclude_pattern_str.endswith("/") - and str(relative_file_path).startswith(exclude_pattern_str) - ): - is_excluded = True - break + if not file_path.is_file(): + continue + + relative_file_path = file_path.relative_to(CLIENT_DIR) + + # Determine file extension, handling dotfiles. + file_extension = "" + if ( + relative_file_path.name.startswith(".") + and "." not in relative_file_path.name[1:] + ): + file_extension = relative_file_path.name.lstrip(".") + else: + file_extension = relative_file_path.suffix.lstrip(".") - if is_excluded: - logger.debug(f"{relative_file_path}: skipped (path excluded)") - continue + if file_extension in EXCLUDE_EXTENSIONS: + logger.debug(f"{relative_file_path}: skipped (extension excluded)") + continue - header_file_path = Path( - os.path.join(HEADER_DIR, f"header-{file_extension}.txt") - ) + # Check if the path should be excluded. + is_excluded = False + for excluded_path in all_excluded_paths: + if relative_file_path == excluded_path or excluded_path in relative_file_path.parents: + is_excluded = True + break - if header_file_path.is_file(): - _prepend_header_to_file(file_path, header_file_path) - logger.debug(f"{relative_file_path}: updated") - else: - logger.error(f"No header compatible with file {relative_file_path}") - sys.exit(2) + if is_excluded: + logger.debug(f"{relative_file_path}: skipped (path excluded)") + continue + + header_file_path = HEADER_DIR / f"header-{file_extension}.txt" + + if header_file_path.is_file(): + _prepend_header_to_file(file_path, header_file_path) + logger.debug(f"{relative_file_path}: updated") + else: + logger.error(f"No header compatible with file {relative_file_path}") + sys.exit(2) logger.info("License fix complete.") def prepare_spec_dir(): logger.info("Preparing spec directory...") spec_dir = Path(SPEC_DIR) - spec_source_dir = Path(os.path.join(CLIENT_DIR.parent.parent, "spec")) + spec_source_dir = CLIENT_DIR.parent.parent / "spec" if spec_source_dir.is_dir(): logger.info(f"Copying spec directory from {spec_source_dir} to {spec_dir}")