diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 2f9a3bc7..c8ea28be 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,9 @@ +# +# Copyright (c) 2026 by Delphix. All rights reserved. +# + [bumpversion] -current_version = 5.0.1 +current_version = 5.1.0 commit = False tag = False parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\.(?P[a-z]+)(?P\d+))? @@ -17,15 +21,17 @@ values = [bumpversion:part:dev] -[bumpversion:file:./dvp/src/main/python/dlpx/virtualization/VERSION] +# Each pyproject.toml's [project].version and sibling-package pins are matched +# by the default {current_version} search; both get updated atomically. +[bumpversion:file:./common/pyproject.toml] -[bumpversion:file:./common/src/main/python/dlpx/virtualization/common/VERSION] +[bumpversion:file:./dvp/pyproject.toml] -[bumpversion:file:./platform/src/main/python/dlpx/virtualization/platform/VERSION] +[bumpversion:file:./libs/pyproject.toml] -[bumpversion:file:./libs/src/main/python/dlpx/virtualization/libs/VERSION] +[bumpversion:file:./platform/pyproject.toml] -[bumpversion:file:./tools/src/main/python/dlpx/virtualization/_internal/VERSION] +[bumpversion:file:./tools/pyproject.toml] [bumpversion:file:./tools/src/test/python/dlpx/virtualization/_internal/test_package_util.py] search = DVP_VERSION = '{current_version}' diff --git a/.claude/skills/vsdk-code-review/SKILL.md b/.claude/skills/vsdk-code-review/SKILL.md new file mode 100644 index 00000000..2b455bf6 --- /dev/null +++ b/.claude/skills/vsdk-code-review/SKILL.md @@ -0,0 +1,325 @@ +--- +name: vsdk-code-review +description: Use when reviewing or writing code in the Delphix Virtualization SDK (virtualization-sdk repo). Covers correctness, exception handling, readability, test quality, and repo conventions (copyright, imports, docstrings, proto conversion, CLI structure). +--- + +# vSDK Code Review + +## Overview + +Reference for the conventions used in the `virtualization-sdk` repo. + +**What CI gates:** `pytest` (all packages, Ubuntu + macOS + Windows) and `flake8 --max-line-length 88` (Ubuntu only). Everything else in this skill is convention enforced through code review. + +Run locally: `sh bin/build_project.sh -f` for flake8, `-t` for tests. + +--- + +## Copyright Headers + +Every source file starts with: + +```python +# +# Copyright (c) YEAR by Delphix. All rights reserved. +# +``` + +- Use the current year for new files; for files modified in a later year use `, `: e.g. `2019, 2026` +- This applies to `.py` files **and** `pyproject.toml` files +- Blank line between the header and the first import or docstring + +--- + +## Imports + +Order: **stdlib → third-party → local**, one blank line between each group. All alphabetical within groups. Matches isort defaults. + +```python +# stdlib +import logging +import os +from contextlib import contextmanager + +# third-party +import click + +# local (dlpx namespace) +from dlpx.virtualization._internal import exceptions, file_util +from dlpx.virtualization._internal.commands import build as build_internal +``` + +Rules: +- **Absolute imports only** — no relative imports (`from . import ...`) +- Multi-line imports use **parentheses**, not backslash continuation +- Align wrapped imports to the opening parenthesis + +--- + +## Formatting + +**Only `flake8` is enforced** — `isort` and `yapf` are dev dependencies but are never run in CI or `build_project.sh`. No repo-level config files for any of them. + +Flake8 runs with `--max-line-length 88` (both CI and `build_project.sh -f`): + +```bash +python -m flake8 src/main/python --max-line-length 88 +python -m flake8 src/test/python --max-line-length 88 +``` + +Key observable style patterns in existing code: +- **Max line length: 88 chars** (not PEP 8's 79) +- Function arguments wrap to align under the opening parenthesis +- `isort` / `yapf` may be run locally as a courtesy but are not gated + +--- + +## Docstrings + +**Google-style** with `Args` and `Returns` sections. Type info goes in the docstring, not in the signature. + +```python +def build(plugin_config, upload_artifact, generate_only, local_vsdk_root=None): + """Builds the plugin using the configuration provided. + + Args: + plugin_config (str): Path to the plugin's config yaml file. + upload_artifact (str): Output file for the build artifact. + generate_only (bool): If True, only generate classes from schemas. + local_vsdk_root (str): Local path to the vSDK repo; None uses PyPI. + + Returns: + str: Path to the generated upload artifact. + """ +``` + +- Opening `"""` on the definition line +- One-line summary, blank line, then details/Args/Returns + +--- + +## Logging + +Every module that logs gets a module-level logger: + +```python +logger = logging.getLogger(__name__) +``` + +Use `logger.debug(...)` for implementation details, `logger.warning(...)` for recoverable issues. Never `print()`. + +--- + +## Exception Hierarchy + +Choose the right class for the context: + +| Package | Exception | When to use | +|---|---|---| +| `tools` | `UserError` | User-visible CLI errors (bad input, missing file). Caught by cli.py and shown to the user. | +| `tools` | `SDKToolingError` | Internal SDK bugs; not user-actionable. | +| `platform` | `UserError` | User-visible errors in operation validation context. | +| `platform` | `IncorrectReturnTypeError` | Operation impl returned the wrong type. | +| `platform` | `OperationNotDefinedError` | `_internal_*` called but impl was never registered. | +| `platform` | `OperationAlreadyDefinedError` | Decorator applied twice to the same operation. | +| `platform` | `DecoratorNotFunctionError` | Decorated object is not a callable. | +| `common` | `PlatformError` | Engine-side bug detected; will be a Fatal on the Delphix Engine. | +| `common` | `PluginRuntimeError` | Plugin-catchable error; plugin code can handle it. | +| `common` | `IncorrectTypeError` | Type validation failure on proto deserialization. | +| `libs` | `LibraryError` | Error from a libs API call; plugin may retry or raise custom error. | +| `libs` | `PluginScriptError` | Non-zero exit from a subprocess/shell execution. | +| `libs` | `IncorrectArgumentTypeError` | Wrong type passed to a libs API function. | + +All exceptions expose a `.message` property. Raise with a helpful string: + +```python +raise exceptions.UserError( + "The src directory {} is not a subdirectory of plugin root {}.".format( + src_dir, plugin_root)) +``` + +--- + +## Proto Conversion + +Classes that wrap protobuf messages implement **bidirectional conversion**: + +```python +def to_proto(self): + """Converts RemoteConnection to common_pb2.RemoteConnection.""" + proto = common_pb2.RemoteConnection() + proto.environment.CopyFrom(self.environment.to_proto()) + proto.user.CopyFrom(self.user.to_proto()) + return proto + +@staticmethod +def from_proto(connection): + """Converts common_pb2.RemoteConnection to RemoteConnection.""" + if not isinstance(connection, common_pb2.RemoteConnection): + raise IncorrectTypeError(RemoteConnection, 'connection', + type(connection), common_pb2.RemoteConnection) + environment = RemoteEnvironment.from_proto(connection.environment) + user = RemoteUser.from_proto(connection.user) + return RemoteConnection(environment=environment, user=user) +``` + +Rules: +- `to_proto()` — instance method, returns the protobuf message +- `from_proto()` — `@staticmethod`, validates type with `isinstance`, returns the wrapper class +- Use `CopyFrom()` for nested proto fields (not direct assignment) +- Chain conversions for nested objects + +--- + +## Namespace Packages + +Each package's `__init__.py` at the `dlpx.virtualization` level must extend the path: + +```python +__path__ = __import__('pkgutil').extend_path(__path__, __name__) +``` + +This allows the five separately-installed packages to share the `dlpx.virtualization` namespace. + +--- + +## Testing Conventions + +- Tests live in `src/test/python/dlpx/virtualization/` mirroring the main source tree +- Each package has a `conftest.py` at `src/test/python/dlpx/virtualization/conftest.py` +- Use `pytest` for test runner; `mock` (not `unittest.mock`) for patching +- Test classes prefixed with `Test`; methods prefixed with `test_` + +```python +import mock +import pytest + +class TestBuildCommand: + + @staticmethod + @pytest.fixture() + def mock_plugin_config(tmp_path): + # fixture setup + ... + + @staticmethod + @mock.patch('dlpx.virtualization._internal.commands.build.os.path.exists') + def test_build_succeeds(mock_exists, mock_plugin_config): + mock_exists.return_value = True + # assertions + ... + + @staticmethod + @pytest.mark.parametrize('level,expected', [ + (logging.DEBUG, 'DEBUG'), + (logging.INFO, 'INFO'), + ]) + def test_log_levels(level, expected): + ... +``` + +Run tests: `python -m pytest src/test/python` from the package directory, or `sh bin/build_project.sh -t -m ` from the repo root. + +--- + +## CLI Structure (tools package) + +`cli.py` is **declarations only** — no business logic: + +```python +# cli.py +@delphix_sdk.command() +@click.argument('plugin_config', ...) +def build(plugin_config, ...): + """One-line help string for the command.""" + build_internal.build(plugin_config, ...) +``` + +All logic goes in `commands/.py`. This is a firm convention — keep `cli.py` as the single source of truth for the CLI surface area. + +--- + +## Correctness + +### vSDK-specific invariants + +- **New operation decorator** (`linked.x()` / `virtual.x()`): the `_internal_*` method must guard with `if not self.x_impl: raise OperationNotDefinedError(Op.X)` before calling the impl. +- **`from_proto()` must validate type** with `isinstance` and raise `IncorrectTypeError` before accessing any field on the proto object. +- **Proto field assignment** must use `CopyFrom()` for nested message fields — direct assignment (`proto.field = other_proto`) silently fails. +- **New wrapper class** exposed to plugin authors: needs both `to_proto()` and `from_proto()` unless it is purely plugin-input (no round-trip needed). +- **`__all__` in `_plugin_classes.py`** must include any new public class; `platform/__init__.py` uses `from _plugin_classes import *` so the wildcard picks it up automatically — but `__all__` must be updated. + +### General correctness + +- No mutable default arguments (`def f(x=[])` — use `None` and assign inside). +- Return values checked at call sites where the caller depends on them. +- No silently swallowed exceptions (`except Exception: pass` or bare `except:`). +- Branching logic covers all cases — check for missing `else` / unhandled enum values. + +--- + +## Exception Handling + +- Catch the **most specific** exception possible; re-raise or log anything unexpected. +- `UserError` messages must be **actionable** — tell the user what went wrong and how to fix it, not just what the code observed. +- Do not catch `UserError` inside library/platform code — let it propagate to `cli.py`. +- When wrapping a lower-level exception, preserve context: `raise UserError("...") from e`. + +--- + +## Readability & Modularity + +- Functions should do one thing. `_internal_*` methods in particular should only do proto conversion + delegation to the impl — no business logic. +- Avoid deeply nested conditionals; early-return or extract helper functions. +- Names should reflect the domain: use `source_config`, `repository`, `snapshot` — not `sc`, `repo`, `snap`. +- Constants belong at module level (UPPERCASE), not buried in function bodies. + +--- + +## Test Quality + +- New operations need tests in `test_plugin.py` covering: successful call, `OperationNotDefinedError` when impl is not set, and `OperationAlreadyDefinedError` when decorated twice. +- Tests should assert **behaviour**, not implementation details (avoid asserting on private attributes like `_impl`). +- Fixtures in `conftest.py` should be extended for new fields (e.g. new `_impl = None` in the operation fixture). +- Parametrized tests (`@pytest.mark.parametrize`) preferred over copy-paste test variants. + +--- + +## Common Review Checklist + +**Style & conventions:** +- [ ] Copyright header present and correct format +- [ ] Imports: stdlib → third-party → local, alphabetical, parentheses for multi-line +- [ ] Google-style docstrings for public functions +- [ ] `logger = logging.getLogger(__name__)` at module level (not `print`) +- [ ] Max line length 88 chars (`flake8 --max-line-length 88`) + +**Correctness:** +- [ ] New operation `_internal_*`: guards `if not self.x_impl: raise OperationNotDefinedError` +- [ ] `from_proto()`: validates type with `isinstance` before field access +- [ ] Proto field assignment uses `CopyFrom()` for nested messages +- [ ] No mutable default arguments; no silently swallowed exceptions +- [ ] Correct exception class for the package/context (see table above) +- [ ] `UserError` messages are actionable (say what went wrong and how to fix it) + +**Architecture:** +- [ ] Proto classes: `to_proto()` + `from_proto()` with type validation +- [ ] `__init__.py` for new `dlpx.virtualization` packages: includes `pkgutil.extend_path` +- [ ] New public class added to `_plugin_classes.__all__` +- [ ] New operations: `Operation` enum value, public decorator name, `_internal_*` method name, and all docstring/comment references use the **same operation name** consistently +- [ ] New required operations: `plugin_validator.py` updated to enforce the operation is implemented +- [ ] New CLI commands: logic in `commands/.py`, not `cli.py` + +**Tests:** +- [ ] Tests mirror the source tree path; use `pytest` + `mock` +- [ ] New operations covered in `test_plugin.py`: success, `OperationNotDefinedError`, `OperationAlreadyDefinedError` +- [ ] `conftest.py` fixtures updated for new `_impl` fields +- [ ] `sh bin/build_project.sh -f` passes (flake8) +- [ ] `sh bin/build_project.sh -t` passes (unit tests) + +**Docs changes** (`docs/` only — skip for pure code PRs): +- [ ] Release note heading format: `# Release - v5.1.0` / `# Breaking Changes - v5.1.0` (no period after `v`) +- [ ] New operation added to `Plugin_Operations.md` summary table, `Decorators.md` table, and `Classes.md` (if a new class was introduced) +- [ ] `Schemas.md` row count updated if a new schema was added +- [ ] New workflow added to `Workflows.md` if referenced from the operations table +- [ ] Internal anchor links use the correct glossary/section slug (not a copy-paste from a similar row) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 373d4f42..22c07c21 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,3 +1,7 @@ +# +# Copyright (c) 2026 by Delphix. All rights reserved. +# + version: 2 updates: - package-ecosystem: pip diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 3ac1ca99..287175b3 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -1,5 +1,5 @@ # -# Copyright (c) 2020, 2022 by Delphix. All rights reserved. +# Copyright (c) 2020, 2026 by Delphix. All rights reserved. # name: "Pre-commit actions for Delphix Virtualization SDK" @@ -14,53 +14,69 @@ on: jobs: pytest311: - name: Test ${{ matrix.package }} on ${{ matrix.os }} using pytest (Python 3.11) + name: Test SDK on ${{ matrix.os }} (Python ${{ matrix.python-version }}) runs-on: ${{ matrix.os }} strategy: - max-parallel: 4 + max-parallel: 3 matrix: python-version: [ 3.11 ] os: [ ubuntu-latest, macos-latest, windows-latest ] - package: [ common, libs, platform, tools ] steps: - - name: Checkout ${{ matrix.package }} project - uses: actions/checkout@v3 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - - name: Install ${{ matrix.package }} dependencies - working-directory: ${{ matrix.package }} + # Install all SDK packages once, in dependency order. Sibling packages + # need to be present in the env before subsequent installs can resolve + # their PEP 621 pins (e.g. dvp-libs needs dvp-common already installed). + - name: Install SDK packages run: | python -m pip install --upgrade pip - pip install -r requirements.txt --find-links https://test.pypi.org/simple/dvp-api/ + pip install -e "./common[dev]" --find-links https://test.pypi.org/simple/dvp-api/ + pip install -e "./libs[dev]" --find-links https://test.pypi.org/simple/dvp-api/ + pip install -e "./platform[dev]" --find-links https://test.pypi.org/simple/dvp-api/ + pip install -e "./tools[dev]" --find-links https://test.pypi.org/simple/dvp-api/ - - name: Install ${{ matrix.package }} project - working-directory: ${{ matrix.package }} - run: | - pip install . --find-links https://test.pypi.org/simple/dvp-api/ + - name: Test common + run: python -m pytest ./common/src/test/python - # Run all the test cases part of the package. - - name: Test ${{ matrix.package }} project with pytest - working-directory: ${{ matrix.package }} - run: | - python -m pytest src/test/python + - name: Test libs + run: python -m pytest ./libs/src/test/python + + - name: Test platform + run: python -m pytest ./platform/src/test/python + + - name: Test tools + run: python -m pytest ./tools/src/test/python - # Install flake8 and run linting on src and test for linting if OS is ubuntu. - name: Install flake8 - if: ${{ matrix.os == 'ubuntu-latest' }} + if: matrix.os == 'ubuntu-latest' + run: pip install flake8 + + - name: Lint common + if: matrix.os == 'ubuntu-latest' run: | - pip install flake8 + python -m flake8 ./common/src/main/python --max-line-length 88 + python -m flake8 ./common/src/test/python --max-line-length 88 - - name: Run flake8 on src directory - if: ${{ matrix.os == 'ubuntu-latest' }} - working-directory: ${{ matrix.package }} - run: python -m flake8 src/main/python --max-line-length 88 + - name: Lint libs + if: matrix.os == 'ubuntu-latest' + run: | + python -m flake8 ./libs/src/main/python --max-line-length 88 + python -m flake8 ./libs/src/test/python --max-line-length 88 + + - name: Lint platform + if: matrix.os == 'ubuntu-latest' + run: | + python -m flake8 ./platform/src/main/python --max-line-length 88 + python -m flake8 ./platform/src/test/python --max-line-length 88 - - name: Run flake8 on test directory - if: ${{ matrix.os == 'ubuntu-latest' }} - working-directory: ${{ matrix.package }} - run: python -m flake8 src/test/python --max-line-length 88 + - name: Lint tools + if: matrix.os == 'ubuntu-latest' + run: | + python -m flake8 ./tools/src/main/python --max-line-length 88 + python -m flake8 ./tools/src/test/python --max-line-length 88 \ No newline at end of file diff --git a/.github/workflows/publish-python-packages.yml b/.github/workflows/publish-python-packages.yml index 2be43842..0c472f57 100644 --- a/.github/workflows/publish-python-packages.yml +++ b/.github/workflows/publish-python-packages.yml @@ -1,14 +1,19 @@ +# +# Copyright (c) 2026 by Delphix. All rights reserved. +# + name: Publish Python packages to Test PyPi on: - # Run on push when the version file has changed on selected branches. + # Run on push when a package version (declared in pyproject.toml) has + # changed on selected branches, or when this workflow itself changes. push: branches: - master - develop - release paths: - - 'dvp/src/main/python/dlpx/virtualization/VERSION' + - '**/pyproject.toml' - '.github/workflows/publish-python-packages.yml' jobs: @@ -21,15 +26,15 @@ jobs: package: [common, dvp, libs, platform, tools] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} # Install dependencies necessary for building and publishing the package. - name: Install dependencies run: | - pip install setuptools wheel twine + pip install build twine # Build each Python package and publish it to Test PyPi. - name: Build and publish ${{ matrix.package }} package working-directory: ${{ matrix.package }} @@ -38,5 +43,5 @@ jobs: TWINE_PASSWORD: ${{ secrets.VSDK_PYPI_TOKEN }} TWINE_REPOSITORY_URL: https://test.pypi.org/legacy/ run: | - python setup.py sdist bdist_wheel + python -m build twine upload dist/* diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..22207637 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,232 @@ +[//]: # (Copyright (c) 2026 by Delphix. All rights reserved.) + +# CLAUDE.md + +This file provides guidance to AI agents working with code in this repository. + +## Project Overview + +The **Delphix Virtualization SDK** — the Python toolkit plugin developers use to build AppData plugins for the Delphix Engine. The repo produces **five Python distributions** that are versioned and shipped together: + +| Package (dist name) | Directory | Purpose | +| --- | --- | --- | +| `dvp` | `dvp/` | Umbrella/meta package | +| `dvp-common` | `common/` | Shared classes/exceptions used by `libs` + `platform` | +| `dvp-libs` | `libs/` | Runtime **Libs** API plugins call on the engine (`run_bash`/`run_powershell`/`run_expect`/`run_sync`, credential helpers) | +| `dvp-platform` | `platform/` | The **Plugin** programming model — `Plugin()` + Discovery/Linked/Virtual/Upgrade operations | +| `dvp-tools` | `tools/` | The `dvp` CLI (plugin build/test/distribution) | + +Two conceptually distinct parts, with different workflows: + +- **`tools`** — the SDK's CLI (`dvp`). Aids plugin development, testing, and distribution. Changes here are isolated from the Delphix Engine. +- **`common` / `libs` / `platform`** — collectively the **"wrappers"**: vanilla Python classes that abstract the Virtualization API protobuf messages (`dvp-api`, **published by app-gate**) away from plugin developers. This is the API plugin developers write against. Changes here affect the plugin build. + +A plugin must package all its dependencies — including `dvp-api` and the wrappers — and `dvp build` does this automatically. + +## Plugin Programming Model + +This is the API the wrappers expose and what `platform`/`libs` code must keep stable for plugin authors: + +- A plugin instantiates `Plugin()` (`from dlpx.virtualization.platform import Plugin`), composed of four operation groups (`platform/_plugin.py`): **`discovery`** (`_discovery.py`), **`linked`** (direct/staged linking, `_linked.py`), **`virtual`** (provisioning virtual datasets, `_virtual.py`), and **`upgrade`** (`_upgrade.py`). +- Authors implement an operation by decorating a method with the plugin object's group, e.g. `@my_plugin.virtual.configure()`, `@my_plugin.discovery.repository()`. The decorator name must start with the plugin variable's name. +- At runtime, plugin code calls the **`libs`** API (`from dlpx.virtualization.libs import ...`) to do remote work on the host: `run_bash` / `run_powershell` / `run_expect`, `run_sync` (rsync), and `retrieve_credentials` / `upgrade_password`. +- **Namespace packages:** all five packages share the `dlpx.virtualization` namespace via `pkgutil.extend_path` in each `__init__.py`, so the separately-installed packages import under one namespace. `platform/__init__.py` re-exports the public symbols — **only symbols exported there are part of the plugin-author API surface.** +- **Proto conversion:** authors never touch protobuf directly. Wrapper data classes implement `to_proto()` / `from_proto()` (operation classes also have `to_protobuf*` helpers); the operation groups' `_internal_*` methods do the protobuf conversion + input validation on the engine side. +- **Adding an operation:** define the constant in `platform/operation.py`, add the decorator + `_internal_*` wrapper to the relevant operations class, export any new public symbols from `platform/__init__.py`, add tests under `platform/src/test/python/`, and update `plugin_validator.py` if the operation should be required for a valid plugin. + +## Repository Layout + +- `common/`, `libs/`, `platform/`, `tools/`, `dvp/` — the five packages, each with its own `pyproject.toml`. +- Each package uses a **Maven-style source layout**: + - main: `/src/main/python/dlpx/virtualization/...` + - tests: `/src/test/python/dlpx/virtualization/...` (with a `conftest.py` per package) +- `bin/` — build/test tooling (`build_project.sh`). +- `docs/` — SDK documentation, built with **MkDocs** (Material theme; `docs/mkdocs.yml`) in its own `pipenv` env (`docs/Pipfile`); `docs/build.sh` runs `pipenv run mkdocs build` and publishes to S3. Public user docs live at https://developer.delphix.com. +- `.bumpversion.cfg` — version config spanning all five packages. + +## Environment & Install + +- **End users** install the published CLI from PyPI: `pip install dvp` (or `dvp==`). Everything below is for SDK *developers*. +- **Python 3.11 only** (`requires-python = ">=3.11, <3.12"`; current series is vSDK 5.x). Develop in a Python 3.11 virtualenv. +- `dvp-api` is hosted on **TestPyPI**, so configure pip with an extra index. Create `/pip.conf`: + ``` + [install] + index-url=https://pypi.org/simple/ + extra-index-url=https://test.pypi.org/simple/ + ``` +- **Editable install of a single package** (with dev tooling): from the package directory, + ``` + pip install -e ".[dev]" + ``` + (The `dev` extra replaces the old `requirements.txt`-based dev install.) + +## Build & Test Commands + +All from the repo root via `bin/build_project.sh` (operates on `common`, `libs`, `platform`, `tools`, `dvp`): + +```bash +sh bin/build_project.sh -h # help +sh bin/build_project.sh -b # build all modules +sh bin/build_project.sh -t # run unit tests for all modules +sh bin/build_project.sh -f # flake8 validation +sh bin/build_project.sh -c # test-coverage mode +sh bin/build_project.sh -v # verbose +sh bin/build_project.sh -m common # restrict to module(s); repeatable: -m common -m libs +# flags combine, e.g.: +sh bin/build_project.sh -bt -m tools # build + test just tools +sh bin/build_project.sh -bft -m tools # build + flake8 + test +sh bin/build_project.sh -bct -m tools # build + coverage + test +``` + +**Single package's unit tests** — from that package directory: +```bash +python -m pytest src/test/python +``` + +## CLI (`tools` / `dvp`) + +- Built with **Click**. The console-script entry point (`tools/pyproject.toml` → `[project.scripts]`) is `dvp = "dlpx.virtualization._internal.cli:delphix_sdk"` (a `@click.group`). Add a subcommand by defining a method in `dlpx.virtualization._internal.cli` annotated `@delphix_sdk.command()` — the method name becomes the command name. +- **Keep Click confined to `cli.py`** — it's the single source of truth for the CLI and must hold **no business logic**; each command delegates immediately into a `_internal/commands/.py` module. +- Commands: `dvp init` (scaffold a plugin), `dvp build` (bundle the plugin + wrappers + `dvp-api` into an upload artifact), `dvp upload` (push the artifact to a Delphix Engine), `dvp download-logs`. +- `dvp build` bundles the wrappers (`common`/`libs`/`platform`) with the plugin: pass `--dev` to build them **from source**, otherwise they're fetched from PyPI. Building from source also needs `~/.dvp/config`: + ``` + [dev] + vsdk_root = /path/to/virtualization-sdk + ``` +- Manually exercise the CLI with `dvp ` after an editable install of `tools`. +- User config `~/.dvp/config`: `[defaults]` (engine/user/password) and `[dev]` (`vsdk_root` for local wrapper builds). Global flags `-v`/`--verbose`, `-q`/`--quiet`. + +Key modules under `tools/src/main/python/dlpx/virtualization/_internal/` (verified to exist; purposes summarized): + +| Module | Purpose | +| --- | --- | +| `cli.py` | Click group + subcommands (declarations only) | +| `commands/build.py` | Build: config validation, codegen, dependency packaging | +| `commands/upload.py` | Upload artifact to the engine + job polling | +| `commands/initialize.py` | Plugin scaffold generation (`dvp init`) | +| `commands/download_logs.py` | Log retrieval from the engine | +| `codegen.py` | Generates Python classes from the plugin's JSON schemas | +| `plugin_util.py` / `plugin_validator.py` / `plugin_importer.py` | Plugin config parsing, decorator/validation, dynamic import | +| `delphix_client.py` | HTTP client for the Delphix Engine REST API | + +**Adding a CLI command:** add a `@delphix_sdk.command()` function in `cli.py`, put the logic in a new `commands/.py`, add tests under `tools/src/test/python/.../commands/`. + +## Conventions + +- **Language**: Python 3.11. Maven-style `src/main/python` + `src/test/python` tree under `dlpx/virtualization/`. +- **Linting**: `flake8` (run via `build_project.sh -f`). +- **Formatting / imports**: `yapf` and `isort` (both in each package's `dev` extra). +- **Testing**: `pytest` (+ `pytest-cov`, `coverage`); `mock`; `httpretty` for HTTP stubbing. Tests live in `src/test/python/...` with per-package `conftest.py`. Provide tests with changes where appropriate. +- **Packaging**: `setuptools` build backend; metadata in each `pyproject.toml`. License is Apache-2.0; sibling-package version pins (e.g. `dvp-tools` → `dvp-libs`/`dvp-platform`) are kept in sync via `.bumpversion.cfg`. +- **Copyright header**: every source file carries `Copyright (c) by Delphix. All rights reserved.` Use `, ` for files edited across years. +- **Lint/format config**: `flake8`/`isort`/`yapf` run on **tool defaults** — there are no repo-level `[tool.flake8]`/`[tool.isort]`/`.flake8` overrides (only `[tool.coverage.run]` in `tools/pyproject.toml`). + +## Exception Hierarchy + +Each package defines its own exceptions (verified in each package's `exceptions.py` / classes): + +- **`common`** — base types reused elsewhere: `PluginRuntimeError`, `IncorrectTypeError`, `PlatformError`. +- **`libs`** — remote-execution failures: `LibraryError`, `PluginScriptError`, `IncorrectArgumentTypeError`. +- **`platform`** — `UserError` plus operation-validation errors: `IncorrectReturnTypeError`, `OperationNotDefinedError`, `OperationAlreadyDefinedError`, `DecoratorNotFunctionError`, `IncorrectUpgradeObjectTypeError`, the `MigrationId*` errors, etc. +- **`tools`** — `SDKToolingError` (internal) and `UserError` (user-facing/actionable), plus CLI-specific errors (`BuildFailedError`, `SchemaValidationError`, `HttpError`, `PluginUploadJobFailed`, `PluginUploadWaitTimedOut`, …). + +## Versioning + +- All five packages (`dvp`, `dvp-common`, `dvp-libs`, `dvp-platform`, `dvp-tools`) are versioned and released **together**, using **semantic versioning** managed by **`bump2version`**. +- Version format: `MAJOR.MINOR.PATCH` (released) or `MAJOR.MINOR.PATCH.dev` (dev builds); config in `.bumpversion.cfg`. + ```bash + bumpversion dev # 1.1.0.dev7 -> 1.1.0.dev8 + bumpversion [major|minor|patch] + bumpversion release # 1.1.0.dev7 -> 1.1.0 + ``` +- A bump updates the `[project].version` (and sibling-package pins) in all five `pyproject.toml`s **and** the `DVP_VERSION` constant in `tools/.../_internal/test_package_util.py` atomically — these targets are listed in `.bumpversion.cfg`. It does **not** commit or tag (`commit = False`, `tag = False`), so commit all the bumped files together yourself. Current version: `5.1.0`. + +## Testing Tiers + +1. **Unit** — `pytest` per package (or `build_project.sh -t`). No engine required. +2. **Manual** — wrapper changes are exercised by building a plugin, uploading to a Delphix Engine, and running standard workflows. +3. **Functional (blackbox)** — run from **app-gate** via `git blackbox` against your pushed SDK branch, e.g.: + ``` + git blackbox -s appdata_python_samples \ + --extra-params="-p virt-sdk-repo=https://github.com//virtualization-sdk.git -p virt-sdk-branch=" + ``` + **Minimum per PR**: `appdata_python_samples` and `appdata_basic` (direct or staged plugin). CLI-focused changes also run the `virtualization_sdk` suite. A non-dev version bump requires QA to create a matching `sdk-x-y-z` toolkit branch first. + +## CI, Gate & Release + +- **CI (GitHub Actions):** the PR check `.github/workflows/pre-commit.yml` runs the **full `pytest` suite across Python 3.11 on Ubuntu, macOS, and Windows** for PRs targeting `master`/`develop`/`release`. It installs the five packages in dependency order (`common` → `libs` → `platform` → `tools`/`dvp`) with `--find-links` to TestPyPI for `dvp-api`. Keep tests green on all three OSes. (Despite its name, this workflow runs cross-OS pytest, not the pre-commit framework.) +- Other workflows: `publish-python-packages.yml` (publishes the five packages), `publish-docs.yml` (publishes docs); `dependabot.yml` manages dependency bumps. +- **Push gate:** `.hooksconfig` defines the Delphix gate for this repo — gatekeeper approval group, Slack push notifications, allowed Jira issue types per branch, and review/comment checks. +- **Manual release (Artifactory):** `sh bin/upload.sh` publishes to the internal dev PyPI (`dvp-local-pypi`); `sh bin/upload.sh --prod` to production (`delphix-local`). Requires `ARTIFACTORY_PYPI_USER` / `ARTIFACTORY_PYPI_PASS`; it reads the version from `.bumpversion.cfg` and uploads with `twine`. + +## Auto-invoked Skills + +When the user asks to raise a review, run `git review`, submit a PR, or open a pull request for code in this repo, automatically invoke the `vsdk-code-review` skill first — do not wait for the user to type `/vsdk-code-review`. If the skill finds any issues, summarize them and ask the user "Proceed with `git review` anyway?" before raising the review. Only proceed if the user confirms. + +## Contributing / Posting Code for Review + +- Fork-based workflow: fork → clone → change → test → bump version → push to a branch on your fork → open a PR to `delphix/virtualization-sdk`. +- **PRs must be based on the current `master` branch** and apply without conflicts. (Default branch is `develop`; CI gates PRs to `master`/`develop`/`release`.) +- **Limit each PR to a single commit that resolves one issue** — squash and rebase onto `master`; for large changes, use a stack of logically independent patches. +- **Commit message format** (see `CONTRIBUTING.md`): start with the GitHub issue id and its title, e.g. `Fixes #123 Format of error is incorrect`, followed by an optional description (each line ≤ 72 chars). If it doesn't address an issue, describe the changes. +- Merges require approval from a **code owner** (per `CODEOWNERS`). +- Bugs and features are filed as **GitHub issues** on `delphix/virtualization-sdk` using the Bug Report / Feature Request templates. + +## Relationship to app-gate + +The Virtualization API protobuf messages (`dvp-api`) are **defined and published by the app-gate repo** (`appliance/server/virtualizationApi`); the wrappers here abstract them for plugin authors. Blackbox tests for this repo are driven from app-gate. Keep wrapper changes compatible with the `dvp-api` version they target. + +## Plugin Operations Reference + +> ⚠ **Verification note:** the decorator names and their operation groups below are **verified against the code** (`platform/_discovery.py` / `_linked.py` / `_virtual.py` / `_upgrade.py`). The **Required / Arguments / Returns** columns are taken from the public plugin-operations docs (https://developer.delphix.com/References/Plugin_Operations/) and were **not** re-verified line-by-line against the operation signatures in this pass — confirm there before relying on exact argument names/return types. Argument names are contractual (must match exactly). Also present in code but omitted from the tables: `linked.source_to_physical()` and `virtual.source_to_physical()`. + +### Discovery +| Decorator | Required | Arguments | Returns | +|---|---|---|---| +| `discovery.repository()` | Yes | `source_connection` | `list[RepositoryDefinition]` | +| `discovery.source_config()` | Yes | `source_connection`, `repository` | `list[SourceConfigDefinition]` | + +### Linked source (dSource) +| Decorator | Required | Arguments | Returns | +|---|---|---|---| +| `linked.pre_snapshot()` | No | `direct_source`\|`staged_source`, `repository`, `source_config`, `optional_snapshot_parameters` | None | +| `linked.post_snapshot()` | Yes | `direct_source`\|`staged_source`, `repository`, `source_config`, `optional_snapshot_parameters` | `SnapshotDefinition` | +| `linked.start_staging()` | No | `staged_source`, `repository`, `source_config` | None | +| `linked.stop_staging()` | No | `staged_source`, `repository`, `source_config` | None | +| `linked.status()` | No | `staged_source`, `repository`, `source_config` | `Status` (defaults `ACTIVE`) | +| `linked.worker()` | No | `staged_source`, `repository`, `source_config` | None | +| `linked.mount_specification()` | Yes (staged) | `staged_source`, `repository` | `MountSpecification` | +| `linked.source_size()` | No | `direct_source`\|`staged_source`, `repository`, `source_config` | numeric | + +### Virtual source (VDB) +| Decorator | Required | Arguments | Returns | +|---|---|---|---| +| `virtual.initialize()` | No | `virtual_source`, `repository` | `SourceConfigDefinition` | +| `virtual.configure()` | Yes | `virtual_source`, `snapshot`, `repository` | `SourceConfigDefinition` | +| `virtual.unconfigure()` | No | `virtual_source`, `repository`, `source_config` | None | +| `virtual.reconfigure()` | Yes | `virtual_source`, `repository`, `source_config`, `snapshot` | `SourceConfigDefinition` | +| `virtual.cleanup()` | No | `virtual_source`, `repository`, `source_config` | None | +| `virtual.start()` / `virtual.stop()` | No | `virtual_source`, `repository`, `source_config` | None | +| `virtual.pre_snapshot()` | No | `virtual_source`, `repository`, `source_config` | None | +| `virtual.post_snapshot()` | Yes | `virtual_source`, `repository`, `source_config` | `SnapshotDefinition` | +| `virtual.mount_specification()` | Yes | `virtual_source`, `repository` | `MountSpecification` | +| `virtual.status()` | No | `virtual_source`, `repository`, `source_config` | `Status` (defaults `ACTIVE`) | +| `virtual.source_size()` | No | `virtual_source`, `repository`, `source_config` | numeric | + +### Data migration (upgrade) +`upgrade.repository(migration_id)`, `upgrade.source_config(...)`, `upgrade.linked_source(...)`, `upgrade.virtual_source(...)`, `upgrade.snapshot(...)` — all optional; each takes the old object **as a plain dict** (property names match the previous schema verbatim) and returns a dict. Migrations run in `migration_id` order. + +Behavioral notes (from the docs): `optional_snapshot_parameters` is `None` for scheduled-policy snapshots (set only on user-triggered syncs); `virtual.unconfigure()` runs on Refresh/Delete/Disable (not just Delete); `virtual.cleanup()` runs after `unconfigure()` in the Delete flow; `virtual.mount_specification()` is the most-triggered required op (Enable/Provision/Refresh/Rollback/Start); `MountSpecification.ownership_specification` is Unix-only and optional. + +## Engine ↔ Plugin Data Flows + +> ⚠ **Verification note:** these describe **engine-side orchestration** (app-gate / Delphix Engine) — the *order* in which the engine invokes plugin operations and how it stores results. They are **not** code in this repo and were **not** verified here; they're carried over from prior documentation. Confirm against the engine docs / SDD before relying on exact ordering or storage details. Useful as a starting map for "trace the full data flow before deciding where a change belongs." + +- **dSource link:** `linked.mount_specification` → `linked.start_staging` → `linked.pre_snapshot` → engine ingests (DIRECT: engine pulls; STAGED: plugin controls transfer) → `linked.post_snapshot` returns a `SnapshotDefinition` the engine persists as snapshot metadata. +- **dSource sync:** `linked.pre_snapshot` (optional) → ingest → `linked.post_snapshot` (new snapshot on the timeflow). +- **VDB provision:** `virtual.mount_specification` → engine clones+mounts → `virtual.configure(virtual_source, snapshot, repository)` → returns `SourceConfigDefinition`. +- **VDB refresh:** `virtual.unconfigure` → `virtual.mount_specification` → `virtual.configure` (newer snapshot). +- **VDB rollback / enable:** `virtual.mount_specification` → `virtual.reconfigure(...)` (+ `virtual.status` to verify on enable). +- **VDB snapshot:** `virtual.pre_snapshot` (optional) → engine snapshots → `virtual.post_snapshot` → `SnapshotDefinition`. +- **VDB delete:** `virtual.stop` (if running) → `virtual.unconfigure` → `virtual.cleanup` → engine unmounts/destroys the clone. +- **Plugin upgrade:** for each stored object the engine calls the matching `upgrade.*` migration (old dict in → new dict out) in `migration_id` order. diff --git a/LICENSE b/LICENSE index d6456956..3d63c06a 100644 --- a/LICENSE +++ b/LICENSE @@ -187,7 +187,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright 2026 Delphix Corp., a Perforce company. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README-dev.md b/README-dev.md index 6a82a865..1581c2b8 100644 --- a/README-dev.md +++ b/README-dev.md @@ -1,4 +1,4 @@ -# Copyright (c) 2019, 2022 by Delphix. All rights reserved. +# Copyright (c) 2019, 2026 by Delphix. All rights reserved. # Delphix Virtualization SDK @@ -45,7 +45,7 @@ These steps are described in more detail below. ### Development environment Development should be done in a personal virtualenv. To setup the virtual environment: -1. `virtualenv /path/to/env/root`. This should be a Python 2.7 virtualenv. +1. `virtualenv /path/to/env/root`. This should be a Python 3.11 virtualenv. 2. `source ~/path/to/env/root/bin/activate`. ### Installing the SDK from source @@ -66,6 +66,10 @@ To install the SDK, follow these steps: 2. To build the project, run `sh bin/build_project.sh -b`. 3. For more information on the script options, use `sh bin/build_project.sh -h`. +To work on a single package in editable mode, install it together with its development +dependencies via the `dev` extra, e.g. `pip install -e ".[dev]"` from the package directory. +This replaces the old `requirements.txt`-based dev install. + ### CLI changes diff --git a/bin/build_project.sh b/bin/build_project.sh index bd938694..93980072 100644 --- a/bin/build_project.sh +++ b/bin/build_project.sh @@ -1,6 +1,6 @@ #!/bin/bash # -# Copyright (c) 2022 by Delphix. All rights reserved. +# Copyright (c) 2026 by Delphix. All rights reserved. # # This script provides functionality to build and run test cases for all python packages. The same script can be used @@ -15,13 +15,23 @@ should_test=false verbose=false coverage=false flake8=false -screenSize=$(tput cols) equalFiller="=" -greenColor=$(tput setaf 10) -orangeColor=$(tput setaf 208) -noColor=$(tput sgr0) -blackColor=$(tput setaf 0) -blueColor=$(tput setaf 31) +failed_steps=() +if [ -t 1 ]; then + screenSize=$(tput cols) + greenColor=$(tput setaf 10) + orangeColor=$(tput setaf 208) + noColor=$(tput sgr0) + blackColor=$(tput setaf 0) + blueColor=$(tput setaf 31) +else + screenSize=100 + greenColor="" + orangeColor="" + noColor="" + blackColor="" + blueColor="" +fi ############################################################ # Help # @@ -86,23 +96,23 @@ run_operations() { if [ "$should_build" = true ]; then echo print_as_per_screen_size " $module_name build starts " "${orangeColor}" ${equalFiller} "${screenSize}" - build_module + build_module || failed_steps+=("$module_name build") print_as_per_screen_size " $module_name build complete " "${greenColor}" ${equalFiller} "${screenSize}" fi if [ "$flake8" = true ]; then echo print_as_per_screen_size " $module_name Flake8 Main starts " "${orangeColor}" ${equalFiller} "${screenSize}" - python -m flake8 "$module_path/src/test/python" --max-line-length 88 + python -m flake8 "$module_path/src/test/python" --max-line-length 88 || failed_steps+=("$module_name flake8 (test)") print_as_per_screen_size " $module_name Flake8 Main complete " "${greenColor}" ${equalFiller} "${screenSize}" echo print_as_per_screen_size " $module_name Flake8 Test starts " "${orangeColor}" ${equalFiller} "${screenSize}" - python -m flake8 "$module_path/src/main/python" --max-line-length 88 + python -m flake8 "$module_path/src/main/python" --max-line-length 88 || failed_steps+=("$module_name flake8 (main)") print_as_per_screen_size " $module_name Flake8 Test complete " "${greenColor}" ${equalFiller} "${screenSize}" fi if [ "$should_test" = true ]; then echo print_as_per_screen_size " $module_name tests starts " "${orangeColor}" ${equalFiller} "${screenSize}" - test_module + test_module || failed_steps+=("$module_name tests") print_as_per_screen_size " $module_name tests complete " "${greenColor}" ${equalFiller} "${screenSize}" fi cd "$current_path" || exit @@ -118,13 +128,11 @@ get_project_path() { # Build the module build_module() { - python setup.py clean --all + rm -rf build/ ./*.egg-info src/main/python/*.egg-info if [ "$verbose" = true ]; then - pip install -r requirements.txt -v - pip install -e . -v + pip install -e ".[dev]" -v else - pip install -r requirements.txt -q - pip install -e . -q + pip install -e ".[dev]" -q fi } @@ -209,7 +217,16 @@ if [ "$should_build" = true ] || [ "$should_test" = true ] || [ "$flake8" = true if [ "$coverage" = true ]; then echo "Paths to combine for coverage are [${paths[*]}]." - coverage combine ${paths[@]} - coverage report -m -i + coverage combine ${paths[@]} || failed_steps+=("coverage combine") + coverage report -m || failed_steps+=("coverage report") + fi + + if [ ${#failed_steps[@]} -gt 0 ]; then + echo + echo "${#failed_steps[@]} step(s) FAILED:" + for step in "${failed_steps[@]}"; do + echo " - $step" + done + exit 1 fi fi diff --git a/common/LICENSE b/common/LICENSE index d6456956..3d63c06a 100644 --- a/common/LICENSE +++ b/common/LICENSE @@ -187,7 +187,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright 2026 Delphix Corp., a Perforce company. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/common/MANIFEST.in b/common/MANIFEST.in index c39fba12..d0dda99f 100644 --- a/common/MANIFEST.in +++ b/common/MANIFEST.in @@ -1,6 +1,5 @@ # -# Copyright (c) 2019 by Delphix. All rights reserved. +# Copyright (c) 2019, 2026 by Delphix. All rights reserved. # include LICENSE -include src/main/python/dlpx/virtualization/common/VERSION diff --git a/common/pyproject.toml b/common/pyproject.toml new file mode 100644 index 00000000..65d0c213 --- /dev/null +++ b/common/pyproject.toml @@ -0,0 +1,48 @@ +# +# Copyright (c) 2026 by Delphix. All rights reserved. +# + +[build-system] +requires = ["setuptools>=77"] +build-backend = "setuptools.build_meta" + +[project] +name = "dvp-common" +version = "5.1.0" +readme = "README.md" +requires-python = ">=3.11, <3.12" +authors = [ + {name = "Delphix", email = "virtualization-plugins@delphix.com"}, +] +license = "Apache-2.0" +license-files = ["LICENSE"] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Programming Language :: Python", + "Programming Language :: Python :: 3.11", + "Operating System :: OS Independent", +] +dependencies = [ + "dvp-api == 1.10.0", + "six >= 1.17, < 1.18", +] + +[project.optional-dependencies] +dev = [ + "bump2version == 1.0.1", + "packaging == 26.0", + "pluggy == 1.6.0", + "pyparsing == 3.3.2", + "pytest == 9.0.3", + "six == 1.17.0", + "zipp == 3.23.0", +] + +[project.urls] +Homepage = "https://developer.delphix.com" + +[tool.setuptools] +package-dir = {"" = "src/main/python"} + +[tool.setuptools.packages.find] +where = ["src/main/python"] \ No newline at end of file diff --git a/common/requirements.txt b/common/requirements.txt deleted file mode 100644 index 822c250f..00000000 --- a/common/requirements.txt +++ /dev/null @@ -1,7 +0,0 @@ -bump2version==1.0.1 -packaging==26.0 -pluggy==1.6.0 -pyparsing==3.3.2 -pytest==9.0.2 -six==1.17.0 -zipp==3.23.0 diff --git a/common/setup.cfg b/common/setup.cfg deleted file mode 100644 index c282c94f..00000000 --- a/common/setup.cfg +++ /dev/null @@ -1,20 +0,0 @@ -# -# Copyright (c) 2019, 2022 by Delphix. All rights reserved. -# - -[metadata] -metadata_version: 1.2 -author: Delphix -author_email: virtualization-plugins@delphix.com -home_page: https://developer.delphix.com -long_description: file: README.md -long_description_content_type: text/markdown -classifiers: - Development Status :: 5 - Production/Stable - Programming Language :: Python - Programming Language :: Python :: 3.11 - License :: OSI Approved :: Apache Software License - Operating System :: OS Independent - -[options] -requires_python: >=3.11, <3.12 diff --git a/common/setup.py b/common/setup.py deleted file mode 100644 index 7ecfe15a..00000000 --- a/common/setup.py +++ /dev/null @@ -1,20 +0,0 @@ -import os -import setuptools - -PYTHON_SRC = 'src/main/python' - -install_requires = [ - "dvp-api == 1.10.0.dev0", - "six >= 1.17, < 1.18", -] - -with open(os.path.join(PYTHON_SRC, 'dlpx/virtualization/common/VERSION')) as version_file: - version = version_file.read().strip() - -setuptools.setup(name='dvp-common', - version=version, - install_requires=install_requires, - package_dir={'': PYTHON_SRC}, - packages=setuptools.find_packages(PYTHON_SRC), - python_requires='>=3.11, <3.12', - ) diff --git a/common/src/main/python/dlpx/virtualization/common/VERSION b/common/src/main/python/dlpx/virtualization/common/VERSION deleted file mode 100644 index 831446cb..00000000 --- a/common/src/main/python/dlpx/virtualization/common/VERSION +++ /dev/null @@ -1 +0,0 @@ -5.1.0 diff --git a/docs/Pipfile b/docs/Pipfile index d6b58fd2..7437f4f4 100644 --- a/docs/Pipfile +++ b/docs/Pipfile @@ -1,3 +1,7 @@ +# +# Copyright (c) 2026 by Delphix. All rights reserved. +# + [[source]] url = "https://pypi.org/simple" verify_ssl = true diff --git a/docs/build.sh b/docs/build.sh index f256f923..27023683 100755 --- a/docs/build.sh +++ b/docs/build.sh @@ -1,4 +1,7 @@ #!/bin/bash +# +# Copyright (c) 2026 by Delphix. All rights reserved. +# git fetch pipenv run mkdocs build --clean diff --git a/docs/docs/References/Dynamic_UI_Schema_Configuration/Overview.md b/docs/docs/References/Dynamic_UI_Schema_Configuration/Overview.md index 2b3c3cd4..2dad668b 100644 --- a/docs/docs/References/Dynamic_UI_Schema_Configuration/Overview.md +++ b/docs/docs/References/Dynamic_UI_Schema_Configuration/Overview.md @@ -28,8 +28,8 @@ Table below lists all the allowed attributes within [dxFormProperties](../Schema | Dynamic UI Schema Configuration | Earliest Supported vSDK Version | Latest Supported vSDK Version | Earliest Supported DE Version | Latest Supported DE Version | |:---------------------------------------------:|:--------------------------------------------:|:-----------------------------------------------:|:-----------------------------:|:-----------------------------------------------------------------:| -| [Collapsible Section](Collapsible_Section.md) | [4.0.2](https://pypi.org/project/dvp/4.0.2/) | [Latest Release](https://pypi.org/project/dvp/) | 14.0.0.0 | [Latest Release](https://cd.delphix.com/docs/latest/new-features) | -| [Hidden Fields](Hidden_Fields.md) | [4.0.2](https://pypi.org/project/dvp/4.0.2/) | [Latest Release](https://pypi.org/project/dvp/) | 14.0.0.0 | [Latest Release](https://cd.delphix.com/docs/latest/new-features) | -| [Text Area](Text_Area.md) | [4.0.2](https://pypi.org/project/dvp/4.0.2/) | [Latest Release](https://pypi.org/project/dvp/) | 14.0.0.0 | [Latest Release](https://cd.delphix.com/docs/latest/new-features) | -| [Validation Messages](Validation_Messages.md) | [4.0.2](https://pypi.org/project/dvp/4.0.2/) | [Latest Release](https://pypi.org/project/dvp/) | 14.0.0.0 | [Latest Release](https://cd.delphix.com/docs/latest/new-features) | +| [Collapsible Section](Collapsible_Section.md) | [4.0.2](https://pypi.org/project/dvp/4.0.2/) | [Latest Release](https://pypi.org/project/dvp/) | 14.0.0.0 | [Latest Release](https://help.delphix.com/cd/current/content/release_notes.htm) | +| [Hidden Fields](Hidden_Fields.md) | [4.0.2](https://pypi.org/project/dvp/4.0.2/) | [Latest Release](https://pypi.org/project/dvp/) | 14.0.0.0 | [Latest Release](https://help.delphix.com/cd/current/content/release_notes.htm) | +| [Text Area](Text_Area.md) | [4.0.2](https://pypi.org/project/dvp/4.0.2/) | [Latest Release](https://pypi.org/project/dvp/) | 14.0.0.0 | [Latest Release](https://help.delphix.com/cd/current/content/release_notes.htm) | +| [Validation Messages](Validation_Messages.md) | [4.0.2](https://pypi.org/project/dvp/4.0.2/) | [Latest Release](https://pypi.org/project/dvp/) | 14.0.0.0 | [Latest Release](https://help.delphix.com/cd/current/content/release_notes.htm) | diff --git a/docs/docs/References/Glossary.md b/docs/docs/References/Glossary.md index a1b949c6..e8af27b5 100644 --- a/docs/docs/References/Glossary.md +++ b/docs/docs/References/Glossary.md @@ -88,7 +88,7 @@ The process of making a virtual copy of a dataset and making it available for us Delphix allows plugins to override the default record size for the linked source and virtual source datafiles. If the “recordSizeInKB” parameter is passed, it will be used to override the default record size and set the new record size for the respective dSources and empty VDBs. VDBs with parent will be inheriting the record size from their parent dSource or VDB. If the parameter is not passed, then default 8K record size will be set for the datafiles. ## Replication -Delphix allows end users to replicate data objects between Delphix Engines by creating a replication profile. Data objects that belong to a plugin can also be part of the replication profile. Refer to the [Delphix Engine Documentation](https://cd.delphix.com/docs/latest/) for more details. +Delphix allows end users to replicate data objects between Delphix Engines by creating a replication profile. Data objects that belong to a plugin can also be part of the replication profile. Refer to the [Delphix Engine Documentation](https://help.delphix.com/cd/current/content/home.htm) for more details. ## Repository Information that represents a set of dependencies that a dataset requires in order to be functional. For example, a particular Postgres database might require an installed Postgres 9.6 DBMS, and so its associated repository would contain all the information required to interact with that DBMS. diff --git a/docs/docs/References/Plugin_Operations.md b/docs/docs/References/Plugin_Operations.md index 816f8c19..c11a915a 100644 --- a/docs/docs/References/Plugin_Operations.md +++ b/docs/docs/References/Plugin_Operations.md @@ -320,6 +320,9 @@ def linked_source_size(direct_source, repository, source_config): Converts a [Direct Linked Source](Glossary.md#direct-linking) to a physical source. Only applies when a physical export is requested on a dSource using a [Direct Linking](Glossary.md#direct-linking) strategy. +!!! info + Can be implemented by any plugin, but the Delphix Engine currently only invokes this operation for UNIX-based environments. + ### Required / Optional **Optional.** @@ -740,6 +743,9 @@ def linked_source_size(staged_source, repository, source_config): Converts a [Staged Linked Source](Glossary.md#staged-linking) to a physical source. Only applies when a physical export is requested on a dSource using a [Staged Linking](Glossary.md#staged-linking) strategy. +!!! info + Can be implemented by any plugin, but the Delphix Engine currently only invokes this operation for UNIX-based environments. + ### Required / Optional **Optional.** @@ -1388,6 +1394,9 @@ def virtual_source_size(virtual_source, repository, source_config): Converts a [Virtual Source](Glossary.md#virtual-source) to a physical source. Only applies when a physical export is requested on a VDB. +!!! info + Can be implemented by any plugin, but the Delphix Engine currently only invokes this operation for UNIX-based environments. + ### Required / Optional **Optional.** diff --git a/docs/docs/References/Schemas.md b/docs/docs/References/Schemas.md index 745d867f..5972db2c 100644 --- a/docs/docs/References/Schemas.md +++ b/docs/docs/References/Schemas.md @@ -434,7 +434,7 @@ An example of a JSON schema using this type is: ``` where `credentialsSupplier` is a definition in the external schema `https://delphix.com/platform/api`. -When providing data for a property of this type, the user has the following four options. +When providing data for a property of this type, the user has the following five options. ##### Option 1: Username and password @@ -541,7 +541,7 @@ For this option, the user must provide data that satisfies this definition: } } ``` -where `type` is a constant that the user interface will submit automatically on behalf of the user, `vault` is a reference to a CyberArk vault configured in the system, and `queryString` is a parameter for locating the credentials in the vault. For details on configuring and using CyberArk vaults, see the [password-vaults documentation for the Delphix engine](https://cd.delphix.com/docs/latest/password-vault-support). +where `type` is a constant that the user interface will submit automatically on behalf of the user, `vault` is a reference to a CyberArk vault configured in the system, and `queryString` is a parameter for locating the credentials in the vault. For details on configuring and using CyberArk vaults, see the [password-vaults documentation for the Delphix engine](https://help.delphix.com/cd/current/content/password_vault_support.htm). Optionally, `expectedSecretType` lets the user constrain the secret returned by the vault to passwords or keys (the default is to allow `any` of those two types of secret). An unexpected type of secret returned by the vault will result in a runtime exception. @@ -593,7 +593,7 @@ For this option, the user must provide data that satisfies this definition: } } ``` -where `type` is a constant that the user interface will submit automatically on behalf of the user, `vault` is a reference to a HashiCorp vault configured in the system, and `engine`, `path`, `usernameKey` and `secretKey` are parameters for locating the credentials in the vault. For details on configuring and using HashiCorp vaults, see the [password-vaults documentation for the Delphix engine](https://cd.delphix.com/docs/latest/password-vault-support). +where `type` is a constant that the user interface will submit automatically on behalf of the user, `vault` is a reference to a HashiCorp vault configured in the system, and `engine`, `path`, `usernameKey` and `secretKey` are parameters for locating the credentials in the vault. For details on configuring and using HashiCorp vaults, see the [password-vaults documentation for the Delphix engine](https://help.delphix.com/cd/current/content/password_vault_support.htm). Optionally, `expectedSecretType` lets the user constrain the secret returned by the vault to passwords or keys (the default is to allow `any` of those two types of secret). An unexpected type of secret returned by the vault will result in a runtime exception. @@ -610,13 +610,64 @@ For example, the user, or the user interface on behalf of the user, can provide: } ``` +##### Option 5: Azure Vault credentials + +For this option, the user must provide data that satisfies this definition: +```json +{ + "type": "object", + "required": ["type", "vault", "azureVaultName", "usernameKey", "secretKey"], + "properties": { + "type": { + "type": "string", + "const": "AzureVaultCredential" + }, + "vault": { + "type": "string", + "format": "reference", + "referenceType": "AzureVault" + }, + "azureVaultName": { + "type": "string" + }, + "usernameKey": { + "type": "string" + }, + "secretKey": { + "type": "string" + }, + "expectedSecretType": { + "type": "string", + "enum": ["any", "password", "keyPair"], + "default": "any" + } + } +} +``` +where `type` is a constant that the user interface will submit automatically on behalf of the user, `vault` is a reference to an Azure Vault configured in the system, and `azureVaultName`, `usernameKey` and `secretKey` are parameters for locating the credentials in the vault. For details on configuring and using Azure vaults, see the [password-vaults documentation for the Delphix engine](https://help.delphix.com/cd/current/content/password_vault_support.htm). + +Optionally, `expectedSecretType` lets the user constrain the secret returned by the vault to passwords or keys (the default is to allow `any` of those two types of secret). An unexpected type of secret returned by the vault will result in a runtime exception. + +For example, the user, or the user interface on behalf of the user, can provide: +```json +"properties": { + "myCredentials": { + "type": "AzureVaultCredential", + "vault": "AZURE_VAULT-1", + "azureVaultName": "my-azure-vault", + "usernameKey": "username", + "secretKey": "password" + } +} +``` + #### `keyCredentialsSupplier` -This object type is identical to `credentialsSupplier` but requires the secrets to be keys. The available options are [keys](#option-2-username-and-keys), [CyberArk vaults](#option-3-cyberark-vault-credentials) and [HashiCorp vaults](#option-4-hashicorp-vault-credentials). The property `expectedSecretType` is required in all cases and must have the value `keyPair`. +This object type is identical to `credentialsSupplier` but requires the secrets to be keys. The available options are [keys](#option-2-username-and-keys), [CyberArk vaults](#option-3-cyberark-vault-credentials), [HashiCorp vaults](#option-4-hashicorp-vault-credentials) and [Azure vaults](#option-5-azure-vault-credentials). The property `expectedSecretType` is required in all cases and must have the value `keyPair`. #### `passwordCredentialsSupplier` -This object type is identical to `credentialsSupplier` but requires the secrets to be passwords. The available options are [passwords](#option-1-username-and-password), [CyberArk vaults](#option-3-cyberark-vault-credentials) and [HashiCorp vaults](#option-4-hashicorp-vault-credentials). The property `expectedSecretType` is required in all cases and must have the value `keyPair`. +This object type is identical to `credentialsSupplier` but requires the secrets to be passwords. The available options are [passwords](#option-1-username-and-password), [CyberArk vaults](#option-3-cyberark-vault-credentials), [HashiCorp vaults](#option-4-hashicorp-vault-credentials) and [Azure vaults](#option-5-azure-vault-credentials). The property `expectedSecretType` is required in all cases and must have the value `keyPair`. ## JSON Schema Limitations diff --git a/docs/docs/References/Version_Compatibility.md b/docs/docs/References/Version_Compatibility.md index 0af640b6..e5365419 100644 --- a/docs/docs/References/Version_Compatibility.md +++ b/docs/docs/References/Version_Compatibility.md @@ -4,16 +4,16 @@ | vSDK Version | Earliest Supported DE Version | Latest Supported DE Version | |------------------------------------------|:-----------------------------:|:-----------------------------------------------------------------:| -| [5.1.0](../Release_Notes/5.1.0/5.1.0.md) | 2026.4.0.0 | [Latest Release](https://cd.delphix.com/docs/latest/new-features) | -| [5.0.1](../Release_Notes/5.0.1/5.0.1.md) | 2025.2.0.0 | [Latest Release](https://cd.delphix.com/docs/latest/new-features) | -| [5.0.0](../Release_Notes/5.0.0/5.0.0.md) | 29.0.0.0 | [Latest Release](https://cd.delphix.com/docs/latest/new-features) | -| [4.1.0](../Release_Notes/4.1.0/4.1.0.md) | 12.0.0.0 | [Latest Release](https://cd.delphix.com/docs/latest/new-features) | -| [4.0.5](../Release_Notes/4.0.5/4.0.5.md) | 6.0.16.0 | [Latest Release](https://cd.delphix.com/docs/latest/new-features) | -| [4.0.2](../Release_Notes/4.0.2/4.0.2.md) | 6.0.12.0 | [Latest Release](https://cd.delphix.com/docs/latest/new-features) | -| [3.1.0](../Release_Notes/3.1.0/3.1.0.md) | 6.0.7.0 | [Latest Release](https://cd.delphix.com/docs/latest/new-features) | -| [3.0.0](../Release_Notes/3.0.0/3.0.0.md) | 6.0.6.0 | [Latest Release](https://cd.delphix.com/docs/latest/new-features) | -| [2.1.0](../Release_Notes/2.1.0/2.1.0.md) | 6.0.3.0 | [Latest Release](https://cd.delphix.com/docs/latest/new-features) | -| [2.0.0](../Release_Notes/2.0.0/2.0.0.md) | 6.0.2.0 | [Latest Release](https://cd.delphix.com/docs/latest/new-features) | +| [5.1.0](../Release_Notes/5.1.0/5.1.0.md) | 2026.4.0.0 | [Latest Release](https://help.delphix.com/cd/current/content/release_notes.htm) | +| [5.0.1](../Release_Notes/5.0.1/5.0.1.md) | 2025.2.0.0 | [Latest Release](https://help.delphix.com/cd/current/content/release_notes.htm) | +| [5.0.0](../Release_Notes/5.0.0/5.0.0.md) | 29.0.0.0 | [Latest Release](https://help.delphix.com/cd/current/content/release_notes.htm) | +| [4.1.0](../Release_Notes/4.1.0/4.1.0.md) | 12.0.0.0 | [Latest Release](https://help.delphix.com/cd/current/content/release_notes.htm) | +| [4.0.5](../Release_Notes/4.0.5/4.0.5.md) | 6.0.16.0 | [Latest Release](https://help.delphix.com/cd/current/content/release_notes.htm) | +| [4.0.2](../Release_Notes/4.0.2/4.0.2.md) | 6.0.12.0 | [Latest Release](https://help.delphix.com/cd/current/content/release_notes.htm) | +| [3.1.0](../Release_Notes/3.1.0/3.1.0.md) | 6.0.7.0 | [Latest Release](https://help.delphix.com/cd/current/content/release_notes.htm) | +| [3.0.0](../Release_Notes/3.0.0/3.0.0.md) | 6.0.6.0 | [Latest Release](https://help.delphix.com/cd/current/content/release_notes.htm) | +| [2.1.0](../Release_Notes/2.1.0/2.1.0.md) | 6.0.3.0 | [Latest Release](https://help.delphix.com/cd/current/content/release_notes.htm) | +| [2.0.0](../Release_Notes/2.0.0/2.0.0.md) | 6.0.2.0 | [Latest Release](https://help.delphix.com/cd/current/content/release_notes.htm) | | [1.0.0](../Release_Notes/1.0.0/1.0.0.md) | 6.0.2.0 | 14.0.0.0 | | [0.4.0](../Release_Notes/0.4.0/0.4.0.md) | 5.3.5.0 | 6.0.1.0 | diff --git a/docs/docs/Versioning_And_Upgrade/Replication.md b/docs/docs/Versioning_And_Upgrade/Replication.md index af5f5433..9471f81c 100644 --- a/docs/docs/Versioning_And_Upgrade/Replication.md +++ b/docs/docs/Versioning_And_Upgrade/Replication.md @@ -1,7 +1,7 @@ # Replication A Delphix Engine (source) can be setup to replicate data objects to another Delphix Engine (target). Plugins built using the Virtualization SDK work seamlessly with Delphix Engine replication with no additional development required from plugin developers. -Only a single version of a plugin can be active on a Delphix Engine at a time. We discuss some basic scenarios below. For more detailed information refer to the [Delphix Engine Documentation](https://cd.delphix.com/docs/latest/). +Only a single version of a plugin can be active on a Delphix Engine at a time. We discuss some basic scenarios below. For more detailed information refer to the [Delphix Engine Documentation](https://help.delphix.com/cd/current/content/home.htm). ## Replica Provisioning Replicated dSource or VDB snapshots can be used to provision new VDBs onto a target Delphix Engine, without failing over any of the objects. When provisioning a VDB from a replicated snapshot: @@ -9,7 +9,7 @@ Replicated dSource or VDB snapshots can be used to provision new VDBs onto a tar * A version of the plugin has to be installed on the target Delphix Engine. * The versions of the plugins installed on the source and target Delphix Engines have to be [compatible](Compatibility.md). -Once provisioned, the VDB on the target Delphix Engine will be associated with the version of the plugin installed on the target Delphix Engine, any required data migrations will be run as part of the provisioning process. For more details refer to the [Delphix Engine Documentation](https://cd.delphix.com/docs/latest/). +Once provisioned, the VDB on the target Delphix Engine will be associated with the version of the plugin installed on the target Delphix Engine, any required data migrations will be run as part of the provisioning process. For more details refer to the [Delphix Engine Documentation](https://help.delphix.com/cd/current/content/home.htm). ## Replication Failover On failover, there are three scenarios for each plugin: @@ -18,4 +18,4 @@ On failover, there are three scenarios for each plugin: | -------- | ------- Source plugin **not installed** on target Delphix Engine | The plugin will be failed over and marked as `active` on the target Delphix Engine. Source plugin version **is equal to** the target plugin version | The plugin from the source will be merged with the plugin on the target Delphix Engine. -Source plugin version **is not equal to** the target plugin version | The plugin from the source will be marked `inactive` on the target Delphix Engine. An `inactive` plugin can be subsequently activated, after failover, if it is [compatible](Compatibility.md) with the existing `active` plugin. Activating a plugin will do an upgrade and merge the `inactive` plugin, and all its associated objects, with the `active` plugin. For more details refer to the [Delphix Engine Documentation](https://cd.delphix.com/docs/latest/). \ No newline at end of file +Source plugin version **is not equal to** the target plugin version | The plugin from the source will be marked `inactive` on the target Delphix Engine. An `inactive` plugin can be subsequently activated, after failover, if it is [compatible](Compatibility.md) with the existing `active` plugin. Activating a plugin will do an upgrade and merge the `inactive` plugin, and all its associated objects, with the `active` plugin. For more details refer to the [Delphix Engine Documentation](https://help.delphix.com/cd/current/content/home.htm). \ No newline at end of file diff --git a/dvp/LICENSE b/dvp/LICENSE index d6456956..3d63c06a 100644 --- a/dvp/LICENSE +++ b/dvp/LICENSE @@ -187,7 +187,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright 2026 Delphix Corp., a Perforce company. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/dvp/MANIFEST.in b/dvp/MANIFEST.in index 0479bae9..40806cc6 100644 --- a/dvp/MANIFEST.in +++ b/dvp/MANIFEST.in @@ -1,6 +1,5 @@ # -# Copyright (c) 2019 by Delphix. All rights reserved. +# Copyright (c) 2019, 2026 by Delphix. All rights reserved. # -include LICENSE -include src/main/python/dlpx/virtualization/VERSION \ No newline at end of file +include LICENSE \ No newline at end of file diff --git a/dvp/pyproject.toml b/dvp/pyproject.toml new file mode 100644 index 00000000..df862ab7 --- /dev/null +++ b/dvp/pyproject.toml @@ -0,0 +1,53 @@ +# +# Copyright (c) 2026 by Delphix. All rights reserved. +# + +[build-system] +requires = ["setuptools>=77"] +build-backend = "setuptools.build_meta" + +[project] +name = "dvp" +version = "5.1.0" +description = "Delphix Virtualization Platform SDK" +readme = "README.md" +requires-python = ">=3.11, <3.12" +authors = [ + {name = "Delphix", email = "virtualization-plugins@delphix.com"}, +] +license = "Apache-2.0" +license-files = ["LICENSE"] +keywords = ["virtualization", "plugin"] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Programming Language :: Python", + "Programming Language :: Python :: 3.11", + "Operating System :: OS Independent", +] +# Sibling-package pins; .bumpversion.cfg keeps these in sync with [project].version. +dependencies = [ + "dvp-common == 5.1.0", + "dvp-libs == 5.1.0", + "dvp-platform == 5.1.0", + "dvp-tools == 5.1.0", +] + +[project.optional-dependencies] +dev = [ + "bump2version == 1.0.1", + "packaging == 26.0", + "pluggy == 1.6.0", + "pyparsing == 3.3.2", + "pytest == 9.0.3", + "six == 1.17.0", + "zipp == 3.23.0", +] + +[project.urls] +Homepage = "https://developer.delphix.com" + +[tool.setuptools] +package-dir = {"" = "src/main/python"} + +[tool.setuptools.packages.find] +where = ["src/main/python"] \ No newline at end of file diff --git a/dvp/requirements.txt b/dvp/requirements.txt deleted file mode 100644 index 822c250f..00000000 --- a/dvp/requirements.txt +++ /dev/null @@ -1,7 +0,0 @@ -bump2version==1.0.1 -packaging==26.0 -pluggy==1.6.0 -pyparsing==3.3.2 -pytest==9.0.2 -six==1.17.0 -zipp==3.23.0 diff --git a/dvp/setup.cfg b/dvp/setup.cfg deleted file mode 100644 index 3952a0c5..00000000 --- a/dvp/setup.cfg +++ /dev/null @@ -1,22 +0,0 @@ -# -# Copyright (c) 2019, 2021 by Delphix. All rights reserved. -# - -[metadata] -metadata_version: 1.2 -author: Delphix -author_email: virtualization-plugins@delphix.com -home_page: https://developer.delphix.com -summary: Delphix Virtualization Platform SDK -long_description: file: README.md -long_description_content_type: text/markdown -keywords: virtualization plugin -classifiers: - Development Status :: 5 - Production/Stable - Programming Language :: Python - Programming Language :: Python :: 3.11 - License :: OSI Approved :: Apache Software License - Operating System :: OS Independent - -[options] -requires_python: >=3.11, <3.12 diff --git a/dvp/setup.py b/dvp/setup.py deleted file mode 100644 index bf0858f5..00000000 --- a/dvp/setup.py +++ /dev/null @@ -1,22 +0,0 @@ -import os -import setuptools - -PYTHON_SRC = 'src/main/python' - -with open(os.path.join(PYTHON_SRC, 'dlpx/virtualization/VERSION')) as version_file: - version = version_file.read().strip() - -install_requires = [ - "dvp-common == {}".format(version), - "dvp-libs == {}".format(version), - "dvp-platform == {}".format(version), - "dvp-tools == {}".format(version) -] - -setuptools.setup(name='dvp', - version=version, - install_requires=install_requires, - package_dir={'': PYTHON_SRC}, - packages=setuptools.find_packages(PYTHON_SRC), - python_requires='>=3.11, <3.12', - ) diff --git a/dvp/src/main/python/dlpx/__init__.py b/dvp/src/main/python/dlpx/__init__.py index c7fd3fc1..2d4d83c5 100644 --- a/dvp/src/main/python/dlpx/__init__.py +++ b/dvp/src/main/python/dlpx/__init__.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2019 by Delphix. All rights reserved. +# Copyright (c) 2019, 2026 by Delphix. All rights reserved. # -__path__ = __import__('pkgutil').extend_path(__path__, __name__) \ No newline at end of file +__path__ = __import__('pkgutil').extend_path(__path__, __name__) diff --git a/dvp/src/main/python/dlpx/virtualization/VERSION b/dvp/src/main/python/dlpx/virtualization/VERSION deleted file mode 100644 index 831446cb..00000000 --- a/dvp/src/main/python/dlpx/virtualization/VERSION +++ /dev/null @@ -1 +0,0 @@ -5.1.0 diff --git a/dvp/src/main/python/dlpx/virtualization/__init__.py b/dvp/src/main/python/dlpx/virtualization/__init__.py index c7fd3fc1..2d4d83c5 100644 --- a/dvp/src/main/python/dlpx/virtualization/__init__.py +++ b/dvp/src/main/python/dlpx/virtualization/__init__.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2019 by Delphix. All rights reserved. +# Copyright (c) 2019, 2026 by Delphix. All rights reserved. # -__path__ = __import__('pkgutil').extend_path(__path__, __name__) \ No newline at end of file +__path__ = __import__('pkgutil').extend_path(__path__, __name__) diff --git a/dvp/src/test/python/test_not_used.py b/dvp/src/test/python/test_not_used.py index 1b369533..62c77b15 100644 --- a/dvp/src/test/python/test_not_used.py +++ b/dvp/src/test/python/test_not_used.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2019 by Delphix. All rights reserved. +# Copyright (c) 2019, 2026 by Delphix. All rights reserved. # @@ -8,4 +8,3 @@ def test_not_used(): The build will fail if there are no tests. This is an empty package needed to tie together the other dvp packages so there's nothing to test. """ - diff --git a/libs/LICENSE b/libs/LICENSE index d6456956..3d63c06a 100644 --- a/libs/LICENSE +++ b/libs/LICENSE @@ -187,7 +187,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright 2026 Delphix Corp., a Perforce company. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/libs/MANIFEST.in b/libs/MANIFEST.in index e99a4c2d..d0dda99f 100644 --- a/libs/MANIFEST.in +++ b/libs/MANIFEST.in @@ -1,6 +1,5 @@ # -# Copyright (c) 2019 by Delphix. All rights reserved. +# Copyright (c) 2019, 2026 by Delphix. All rights reserved. # include LICENSE -include src/main/python/dlpx/virtualization/libs/VERSION diff --git a/libs/pyproject.toml b/libs/pyproject.toml new file mode 100644 index 00000000..f7a4c126 --- /dev/null +++ b/libs/pyproject.toml @@ -0,0 +1,52 @@ +# +# Copyright (c) 2026 by Delphix. All rights reserved. +# + +[build-system] +requires = ["setuptools>=77"] +build-backend = "setuptools.build_meta" + +[project] +name = "dvp-libs" +version = "5.1.0" +description = "Delphix Virtualization Platform Libraries" +readme = "README.md" +requires-python = ">=3.11, <3.12" +authors = [ + {name = "Delphix", email = "virtualization-plugins@delphix.com"}, +] +license = "Apache-2.0" +license-files = ["LICENSE"] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Programming Language :: Python", + "Programming Language :: Python :: 3.11", + "Operating System :: OS Independent", +] +# dvp-common pin is kept in sync with [project].version via .bumpversion.cfg. +dependencies = [ + "dvp-api == 1.10.0", + "dvp-common == 5.1.0", + "six >= 1.17, < 1.18", +] + +[project.optional-dependencies] +dev = [ + "bump2version == 1.0.1", + "mock == 5.2.0", + "packaging == 26.0", + "pluggy == 1.6.0", + "pyparsing == 3.3.2", + "pytest == 9.0.3", + "six == 1.17.0", + "zipp == 3.23.0", +] + +[project.urls] +Homepage = "https://developer.delphix.com" + +[tool.setuptools] +package-dir = {"" = "src/main/python"} + +[tool.setuptools.packages.find] +where = ["src/main/python"] \ No newline at end of file diff --git a/libs/requirements.txt b/libs/requirements.txt deleted file mode 100644 index 8b37cc01..00000000 --- a/libs/requirements.txt +++ /dev/null @@ -1,9 +0,0 @@ -./../common -bump2version==1.0.1 -mock==5.2.0 -packaging==26.0 -pluggy==1.6.0 -pyparsing==3.3.2 -pytest==9.0.2 -six==1.17.0 -zipp==3.23.0 diff --git a/libs/setup.cfg b/libs/setup.cfg deleted file mode 100644 index e934522d..00000000 --- a/libs/setup.cfg +++ /dev/null @@ -1,21 +0,0 @@ -# -# Copyright (c) 2019, 2022 by Delphix. All rights reserved. -# - -[metadata] -metadata_version: 1.2 -author: Delphix -author_email: virtualization-plugins@delphix.com -home_page: https://developer.delphix.com -summary: Delphix Virtualization Platform Libraries -long_description: file: README.md -long_description_content_type: text/markdown -classifiers: - Development Status :: 5 - Production/Stable - Programming Language :: Python - Programming Language :: Python :: 3.11 - License :: OSI Approved :: Apache Software License - Operating System :: OS Independent - -[options] -requires_python: >=3.11, <3.12 diff --git a/libs/setup.py b/libs/setup.py deleted file mode 100644 index 4f69b703..00000000 --- a/libs/setup.py +++ /dev/null @@ -1,21 +0,0 @@ -import os -import setuptools - -PYTHON_SRC = 'src/main/python' - -with open(os.path.join(PYTHON_SRC, 'dlpx/virtualization/libs/VERSION')) as version_file: - version = version_file.read().strip() - -install_requires = [ - "dvp-api == 1.10.0.dev0", - "dvp-common == {}".format(version), - "six >= 1.17, < 1.18", -] - -setuptools.setup(name='dvp-libs', - version=version, - install_requires=install_requires, - package_dir={'': PYTHON_SRC}, - packages=setuptools.find_packages(PYTHON_SRC), - python_requires='>=3.11, <3.12', - ) diff --git a/libs/src/main/python/dlpx/virtualization/libs/VERSION b/libs/src/main/python/dlpx/virtualization/libs/VERSION deleted file mode 100644 index 831446cb..00000000 --- a/libs/src/main/python/dlpx/virtualization/libs/VERSION +++ /dev/null @@ -1 +0,0 @@ -5.1.0 diff --git a/libs/src/test/python/dlpx/virtualization/_engine/__init__.py b/libs/src/test/python/dlpx/virtualization/_engine/__init__.py index e69de29b..f256b614 100644 --- a/libs/src/test/python/dlpx/virtualization/_engine/__init__.py +++ b/libs/src/test/python/dlpx/virtualization/_engine/__init__.py @@ -0,0 +1,3 @@ +# +# Copyright (c) 2026 by Delphix. All rights reserved. +# diff --git a/linkcheck-skip.txt b/linkcheck-skip.txt index 58bb60b0..5f5e382a 100644 --- a/linkcheck-skip.txt +++ b/linkcheck-skip.txt @@ -1,3 +1,6 @@ +# +# Copyright (c) 2026 by Delphix. All rights reserved. +# # URLs to skip when running linkcheck (https://github.com/filiph/linkcheck) # Skip certain external links fonts.gstatic.com diff --git a/platform/LICENSE b/platform/LICENSE index d6456956..3d63c06a 100644 --- a/platform/LICENSE +++ b/platform/LICENSE @@ -187,7 +187,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright 2026 Delphix Corp., a Perforce company. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/platform/MANIFEST.in b/platform/MANIFEST.in index 5cbde639..d0dda99f 100644 --- a/platform/MANIFEST.in +++ b/platform/MANIFEST.in @@ -1,6 +1,5 @@ # -# Copyright (c) 2019 by Delphix. All rights reserved. +# Copyright (c) 2019, 2026 by Delphix. All rights reserved. # include LICENSE -include src/main/python/dlpx/virtualization/platform/VERSION diff --git a/platform/pyproject.toml b/platform/pyproject.toml new file mode 100644 index 00000000..f03e2346 --- /dev/null +++ b/platform/pyproject.toml @@ -0,0 +1,52 @@ +# +# Copyright (c) 2026 by Delphix. All rights reserved. +# + +[build-system] +requires = ["setuptools>=77"] +build-backend = "setuptools.build_meta" + +[project] +name = "dvp-platform" +version = "5.1.0" +description = "Delphix Virtualization Platform APIs" +readme = "README.md" +requires-python = ">=3.11, <3.12" +authors = [ + {name = "Delphix", email = "virtualization-plugins@delphix.com"}, +] +license = "Apache-2.0" +license-files = ["LICENSE"] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Programming Language :: Python", + "Programming Language :: Python :: 3.11", + "Operating System :: OS Independent", +] +# dvp-common pin is kept in sync with [project].version via .bumpversion.cfg. +dependencies = [ + "dvp-api == 1.10.0", + "dvp-common == 5.1.0", + "six >= 1.17, < 1.18", +] + +[project.optional-dependencies] +dev = [ + "bump2version == 1.0.1", + "mock == 5.2.0", + "packaging == 26.0", + "pluggy == 1.6.0", + "pyparsing == 3.3.2", + "pytest == 9.0.3", + "six == 1.17.0", + "zipp == 3.23.0", +] + +[project.urls] +Homepage = "https://developer.delphix.com" + +[tool.setuptools] +package-dir = {"" = "src/main/python"} + +[tool.setuptools.packages.find] +where = ["src/main/python"] \ No newline at end of file diff --git a/platform/requirements.txt b/platform/requirements.txt deleted file mode 100644 index 8b37cc01..00000000 --- a/platform/requirements.txt +++ /dev/null @@ -1,9 +0,0 @@ -./../common -bump2version==1.0.1 -mock==5.2.0 -packaging==26.0 -pluggy==1.6.0 -pyparsing==3.3.2 -pytest==9.0.2 -six==1.17.0 -zipp==3.23.0 diff --git a/platform/setup.cfg b/platform/setup.cfg deleted file mode 100644 index 7194761a..00000000 --- a/platform/setup.cfg +++ /dev/null @@ -1,21 +0,0 @@ -# -# Copyright (c) 2019, 2022 by Delphix. All rights reserved. -# - -[metadata] -metadata_version: 1.2 -author: Delphix -author_email: virtualization-plugins@delphix.com -home_page: https://developer.delphix.com -summary: Delphix Virtualization Platform APIs -long_description: file: README.md -long_description_content_type: text/markdown -classifiers: - Development Status :: 5 - Production/Stable - Programming Language :: Python - Programming Language :: Python :: 3.11 - License :: OSI Approved :: Apache Software License - Operating System :: OS Independent - -[options] -requires_python: >=3.11, <3.12 diff --git a/platform/setup.py b/platform/setup.py deleted file mode 100644 index 3377837b..00000000 --- a/platform/setup.py +++ /dev/null @@ -1,21 +0,0 @@ -import os -import setuptools - -PYTHON_SRC = 'src/main/python' - -with open(os.path.join(PYTHON_SRC, 'dlpx/virtualization/platform/VERSION')) as version_file: - version = version_file.read().strip() - -install_requires = [ - "dvp-api == 1.10.0.dev0", - "dvp-common == {}".format(version), - "six >= 1.17, < 1.18", -] - -setuptools.setup(name='dvp-platform', - version=version, - install_requires=install_requires, - package_dir={'': PYTHON_SRC}, - packages=setuptools.find_packages(PYTHON_SRC), - python_requires='>=3.11, <3.12', - ) diff --git a/platform/src/main/python/dlpx/virtualization/platform/VERSION b/platform/src/main/python/dlpx/virtualization/platform/VERSION deleted file mode 100644 index 831446cb..00000000 --- a/platform/src/main/python/dlpx/virtualization/platform/VERSION +++ /dev/null @@ -1 +0,0 @@ -5.1.0 diff --git a/platform/src/test/python/dlpx/virtualization/fake_generated_definitions.py b/platform/src/test/python/dlpx/virtualization/fake_generated_definitions.py index 9b5d087c..0c770fc8 100644 --- a/platform/src/test/python/dlpx/virtualization/fake_generated_definitions.py +++ b/platform/src/test/python/dlpx/virtualization/fake_generated_definitions.py @@ -1,3 +1,7 @@ +# +# Copyright (c) 2026 by Delphix. All rights reserved. +# + import six diff --git a/tools/LICENSE b/tools/LICENSE index d6456956..3d63c06a 100644 --- a/tools/LICENSE +++ b/tools/LICENSE @@ -187,7 +187,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright 2026 Delphix Corp., a Perforce company. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/tools/MANIFEST.in b/tools/MANIFEST.in index a9138fbd..e5ec846d 100644 --- a/tools/MANIFEST.in +++ b/tools/MANIFEST.in @@ -1,9 +1,7 @@ # -# Copyright (c) 2019 by Delphix. All rights reserved. +# Copyright (c) 2019, 2026 by Delphix. All rights reserved. # -exclude README-dev.md - include LICENSE include src/main/python/dlpx/virtualization/_internal/codegen/swagger-codegen-cli-2.3.1.jar include src/main/python/dlpx/virtualization/_internal/codegen/codegen-config.json @@ -11,4 +9,3 @@ recursive-include src/main/python/dlpx/virtualization/_internal/codegen/template recursive-include src/main/python/dlpx/virtualization/_internal/commands/plugin_template * recursive-include src/main/python/dlpx/virtualization/_internal/validation_schemas * recursive-include src/main/python *.cfg -include src/main/python/dlpx/virtualization/_internal/VERSION diff --git a/tools/README-dev.md b/tools/README-dev.md index 740a1e1d..303f4b8c 100644 --- a/tools/README-dev.md +++ b/tools/README-dev.md @@ -1,3 +1,5 @@ +# Copyright (c) 2026 by Delphix. All rights reserved. + # Delphix Virtualization SDK Tools ## Purpose @@ -9,7 +11,7 @@ and upload virtualization plugins. ### Development Environment To setup the development environment, follow the instructions in [README-dev.md](https://github.com/delphix/virtualization-sdk/blob/develop/README-dev.md) -For quick iterations, install the `tools` package in editable mode (`pip install -e .`). This means that changes to the +For quick iterations, install the `tools` package and its development dependencies in editable mode (`pip install -e ".[dev]"`). This means that changes to the code will automatically be reflected in your environment. You will not need to reinstall the tools module each time a change is made. diff --git a/tools/pyproject.toml b/tools/pyproject.toml new file mode 100644 index 00000000..9a60701a --- /dev/null +++ b/tools/pyproject.toml @@ -0,0 +1,89 @@ +# +# Copyright (c) 2026 by Delphix. All rights reserved. +# + +[build-system] +requires = ["setuptools>=77"] +build-backend = "setuptools.build_meta" + +[project] +name = "dvp-tools" +version = "5.1.0" +description = "Delphix Virtualization SDK Tools" +readme = "README.md" +requires-python = ">=3.11, <3.12" +authors = [ + {name = "Delphix", email = "virtualization-plugins@delphix.com"}, +] +license = "Apache-2.0" +license-files = ["LICENSE"] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Programming Language :: Python", + "Programming Language :: Python :: 3.11", + "Operating System :: OS Independent", +] +# Sibling-package pins (dvp-libs, dvp-platform) are kept in sync via .bumpversion.cfg. +dependencies = [ + "attrs >= 25.3, < 25.4", + "certifi >= 2024, < 2025", + "click == 7.1.2", + "click-configfile == 0.2.3", + "configparser >= 7.2, < 7.3", + "dvp-libs == 5.1.0", + "dvp-platform == 5.1.0", + "flake8 >= 7.3, < 7.4", + "httpretty >= 1.0, < 1.1", + "importlib-resources >= 6.5, < 6.6", + "jinja2 >= 3.1, < 3.2", + "jsonschema >= 4.25, < 4.26", + "MarkupSafe >= 3.0, < 3.1", + "pkgutil_resolve_name == 1.3.10", + "pyyaml >= 6, < 7", + "requests >= 2.32, < 2.33", + "six >= 1.17, < 1.18", + "zipp >= 3.23, < 3.24", +] + +[project.optional-dependencies] +dev = [ + "bump2version == 1.0.1", + "coverage == 7.13.5", + "entrypoints == 0.4", + "flake8 == 7.3.0", + "httpretty == 1.0.5", + "isort == 8.0.1", + "mccabe == 0.7.0", + "mock == 5.2.0", + "more-itertools == 10.8.0", + "packaging == 26.0", + "pluggy == 1.6.0", + "pycodestyle == 2.14.0", + "pyflakes == 3.4.0", + "pyparsing == 3.3.2", + "pytest == 9.0.3", + "pytest-cov == 7.1.0", + "six == 1.17.0", + "yapf == 0.43.0", + "zipp == 3.23.0", +] + +[project.urls] +Homepage = "https://developer.delphix.com" + +[project.scripts] +dvp = "dlpx.virtualization._internal.cli:delphix_sdk" + +[tool.setuptools] +package-dir = {"" = "src/main/python"} +include-package-data = true + +[tool.setuptools.packages.find] +where = ["src/main/python"] + +[tool.coverage.run] +source = ["src/main/python"] +omit = [ + "*/plugin_template/*", + "*.template", +] \ No newline at end of file diff --git a/tools/requirements.txt b/tools/requirements.txt deleted file mode 100644 index 5ba42cdb..00000000 --- a/tools/requirements.txt +++ /dev/null @@ -1,22 +0,0 @@ -./../common -./../libs -./../platform -bump2version==1.0.1 -coverage==7.13.5 -entrypoints==0.4 -flake8==7.3.0 -httpretty==1.0.5 -isort==8.0.1 -mccabe==0.7.0 -mock==5.2.0 -more-itertools==10.8.0 -packaging==26.0 -pluggy==1.6.0 -pycodestyle==2.14.0 -pyflakes==3.4.0 -pyparsing==3.3.2 -pytest-cov==7.1.0 -pytest==9.0.2 -six==1.17.0 -yapf==0.43.0 -zipp==3.23.0 diff --git a/tools/setup.cfg b/tools/setup.cfg deleted file mode 100644 index d4c1bf04..00000000 --- a/tools/setup.cfg +++ /dev/null @@ -1,26 +0,0 @@ -# -# Copyright (c) 2019, 2021 by Delphix. All rights reserved. -# - -[metadata] -metadata_version: 1.2 -author: Delphix -author_email: virtualization-plugins@delphix.com -home_page: https://developer.delphix.com -summary: Delphix Virtualization SDK Tools -long_description: file: README.md -long_description_content_type: text/markdown -classifiers: - Development Status :: 5 - Production/Stable - Programming Language :: Python - Programming Language :: Python :: 3.11 - License :: OSI Approved :: Apache Software License - Operating System :: OS Independent - -[options] -include_package_data = True -requires_python: >=3.11, <3.12 - -[options.entry_points] -console_scripts = - dvp = dlpx.virtualization._internal.cli:delphix_sdk diff --git a/tools/setup.py b/tools/setup.py deleted file mode 100644 index d3eb0ed6..00000000 --- a/tools/setup.py +++ /dev/null @@ -1,43 +0,0 @@ -import os -import setuptools - -PYTHON_SRC = 'src/main/python' - -with open(os.path.join(PYTHON_SRC, 'dlpx/virtualization/_internal/VERSION')) as version_file: - version = version_file.read().strip() - -# -# Update the dependency using below use cases -# 1. Dependency version change does not break test cases or have code issues -# - Only update the maximum version (<). -# 2. Dependency version changes break test cases or have code issues -# - Update the minimum as well as maximum version along with code changes. -# -install_requires = [ - "attrs >= 25.3, < 25.4", - "certifi >= 2024, < 2025", - "click == 7.1.2", - "click-configfile == 0.2.3", - "configparser >= 7.2, < 7.3", - "dvp-libs == {}".format(version), - "dvp-platform == {}".format(version), - "flake8 >= 7.3, < 7.4", - "httpretty >= 1.0, < 1.1", - "importlib-resources >= 6.5, < 6.6", - "jinja2 >= 3.1, < 3.2", - "jsonschema >= 4.25, < 4.26", - "MarkupSafe >= 3.0, < 3.1", - "pkgutil_resolve_name == 1.3.10", - "pyyaml >= 6, < 7", - "requests >= 2.32, < 2.33", - "six >= 1.17, < 1.18", - "zipp >= 3.23, < 3.24", -] - -setuptools.setup(name='dvp-tools', - version=version, - install_requires=install_requires, - package_dir={'': PYTHON_SRC}, - packages=setuptools.find_packages(PYTHON_SRC), - python_requires='>=3.11, <3.12', - ) diff --git a/tools/src/main/python/dlpx/virtualization/_internal/VERSION b/tools/src/main/python/dlpx/virtualization/_internal/VERSION deleted file mode 100644 index 831446cb..00000000 --- a/tools/src/main/python/dlpx/virtualization/_internal/VERSION +++ /dev/null @@ -1 +0,0 @@ -5.1.0 diff --git a/tools/src/main/python/dlpx/virtualization/_internal/package_util.py b/tools/src/main/python/dlpx/virtualization/_internal/package_util.py index b065a380..ce2fee0b 100644 --- a/tools/src/main/python/dlpx/virtualization/_internal/package_util.py +++ b/tools/src/main/python/dlpx/virtualization/_internal/package_util.py @@ -6,8 +6,11 @@ import logging import os import re +from importlib.metadata import PackageNotFoundError +from importlib.metadata import version as _pkg_version from dlpx.virtualization import _internal as virtualization_internal +from dlpx.virtualization._internal import exceptions from dlpx.virtualization.platform import util from six.moves import configparser @@ -45,11 +48,12 @@ def _get_settings(): @_run_once def get_version(): - """Returns the version of the dlpx.virtualization._internal package.""" - with open(os.path.join(get_internal_package_root(), - 'VERSION')) as version_file: - version = version_file.read().strip() - return version + """Returns the version of the installed dvp-tools package.""" + try: + return _pkg_version("dvp-tools") + except PackageNotFoundError: + raise exceptions.UserError( + "dvp-tools is not installed. Run 'pip install dvp'.") def get_external_version_string(version_string): diff --git a/tools/src/main/python/dlpx/virtualization/_internal/plugin_dependency_util.py b/tools/src/main/python/dlpx/virtualization/_internal/plugin_dependency_util.py index 4ae8ea88..ff269092 100644 --- a/tools/src/main/python/dlpx/virtualization/_internal/plugin_dependency_util.py +++ b/tools/src/main/python/dlpx/virtualization/_internal/plugin_dependency_util.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2019, 2021 by Delphix. All rights reserved. +# Copyright (c) 2019, 2021, 2026 by Delphix. All rights reserved. # import compileall @@ -45,12 +45,12 @@ def install_deps(target_dir, local_vsdk_root=None): # # Build the wheels for each package in a temporary directory. # - # Pip supports installing directly from a setup.py file but this + # Pip supports installing directly from a package source tree but this # proved to be incredibly slow due to how it copies source files. # If that issue is resolved, it would likely be better to use pip to - # install directly from the setup.py file instead of needing to build - # the wheels first. This would remove the need for a temp directory - # as well. + # install directly from the package directory instead of needing to + # build the wheels first. This would remove the need for a temp + # directory as well. # with file_util.tmpdir() as wheel_dir: for package in package_names: @@ -147,22 +147,23 @@ def _pip_install_to_dir(dependencies, target_dir): def _build_wheel(package_root, target_dir=None): """ - Uses the 'setup.py' file in package_root to build a wheel distribution. If - target_dir is present, the wheel is built into it. Raises a - SubprocessFailedError if it fails. + Uses the 'pyproject.toml' file in package_root to build a wheel + distribution via pip (PEP 517). If target_dir is present, the wheel is + built into it. Raises a SubprocessFailedError if it fails. Args: package_root: The path to the root of the package to build. It is - assumed there is a setup.py file in this directory. + assumed there is a pyproject.toml file in this directory. target_dir: The directory to build the wheel into. """ - if not os.path.exists(os.path.join(package_root, 'setup.py')): + if not os.path.exists(os.path.join(package_root, 'pyproject.toml')): raise RuntimeError( - 'No setup.py file exists in directory {}'.format(package_root)) + 'No pyproject.toml file exists in directory {}'.format( + package_root)) - args = [sys.executable, 'setup.py', 'bdist_wheel'] + args = [sys.executable, '-m', 'pip', 'wheel', '--no-deps', '.'] if target_dir: - args.extend(['-d', target_dir]) + args.extend(['-w', target_dir]) logger.debug('Executing %s', ' '.join(args)) proc = subprocess.Popen(args, diff --git a/tools/src/test/python/dlpx/virtualization/_internal/engine_version.cfg b/tools/src/test/python/dlpx/virtualization/_internal/engine_version.cfg index a3b69e2e..b9046630 100644 --- a/tools/src/test/python/dlpx/virtualization/_internal/engine_version.cfg +++ b/tools/src/test/python/dlpx/virtualization/_internal/engine_version.cfg @@ -1,5 +1,5 @@ # -# Copyright (c) 2022 by Delphix. All rights reserved. +# Copyright (c) 2026 by Delphix. All rights reserved. # # diff --git a/tools/src/test/python/dlpx/virtualization/_internal/test_cli.py b/tools/src/test/python/dlpx/virtualization/_internal/test_cli.py index 49c673bf..2ec8ad0b 100644 --- a/tools/src/test/python/dlpx/virtualization/_internal/test_cli.py +++ b/tools/src/test/python/dlpx/virtualization/_internal/test_cli.py @@ -198,12 +198,17 @@ def test_empty_root_dir(plugin_name): assert "Invalid value for '-r'" in result.output @staticmethod - def test_name_required(): + @mock.patch('dlpx.virtualization._internal.commands.initialize.init') + def test_name_optional(mock_init): + # --plugin-name is optional (PYT-536); when omitted, the plugin id + # (auto-generated UUID) is used as the display name. runner = click_testing.CliRunner() result = runner.invoke(cli.delphix_sdk, ['init']) - assert result.exit_code != 0 + assert result.exit_code == 0, 'Output: {}'.format(result.output) + mock_init.assert_called_once_with(os.getcwd(), const.DIRECT_TYPE, + None, const.UNIX_HOST_TYPE) @staticmethod def test_multiple_host_types(): diff --git a/tools/src/test/python/dlpx/virtualization/_internal/test_plugin_dependency_util.py b/tools/src/test/python/dlpx/virtualization/_internal/test_plugin_dependency_util.py index 2555ed87..1b705900 100644 --- a/tools/src/test/python/dlpx/virtualization/_internal/test_plugin_dependency_util.py +++ b/tools/src/test/python/dlpx/virtualization/_internal/test_plugin_dependency_util.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2019, 2020 by Delphix. All rights reserved. +# Copyright (c) 2019, 2020, 2026 by Delphix. All rights reserved. # import os @@ -114,8 +114,8 @@ def test_install_to_dir(mock_popen): @staticmethod @mock.patch.object(subprocess, 'Popen') def test_build_wheel(mock_popen, tmp_path): - setup_file = tmp_path / 'setup.py' - setup_file.touch() + pyproject_file = tmp_path / 'pyproject.toml' + pyproject_file.touch() mock_popen.return_value.communicate.return_value = ('output', '') mock_popen.return_value.wait.return_value = 0 @@ -123,24 +123,25 @@ def test_build_wheel(mock_popen, tmp_path): pdu._build_wheel(tmp_path.as_posix()) mock_popen.assert_called_once_with( - [sys.executable, 'setup.py', 'bdist_wheel'], + [sys.executable, '-m', 'pip', 'wheel', '--no-deps', '.'], cwd=tmp_path.as_posix(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT) @staticmethod - def test_build_wheel_fails_with_no_setup_file(tmp_path): + def test_build_wheel_fails_with_no_pyproject_file(tmp_path): with pytest.raises(RuntimeError) as excinfo: pdu._build_wheel(tmp_path.as_posix()) - assert str(excinfo.value) == ('No setup.py file exists in directory ' - '{}'.format(tmp_path.as_posix())) + assert str(excinfo.value) == ('No pyproject.toml file exists in ' + 'directory {}'.format( + tmp_path.as_posix())) @staticmethod @mock.patch.object(subprocess, 'Popen') def test_build_wheel_non_zero_exit(mock_popen, tmp_path): - setup_file = tmp_path / 'setup.py' - setup_file.touch() + pyproject_file = tmp_path / 'pyproject.toml' + pyproject_file.touch() mock_popen.return_value.communicate.return_value = ('output', '') mock_popen.return_value.wait.return_value = 1 @@ -150,8 +151,10 @@ def test_build_wheel_non_zero_exit(mock_popen, tmp_path): e = excinfo.value - expected_args = [sys.executable, 'setup.py', 'bdist_wheel'] - mock_popen.asesrt_called_once_with(expected_args, + expected_args = [ + sys.executable, '-m', 'pip', 'wheel', '--no-deps', '.' + ] + mock_popen.assert_called_once_with(expected_args, cwd=tmp_path.as_posix(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT) @@ -164,10 +167,10 @@ def test_build_wheel_non_zero_exit(mock_popen, tmp_path): @mock.patch.object(subprocess, 'Popen') def test_build_wheel_target_dir(mock_popen, tmp_path): package_dir = tmp_path / 'pkg' - setup_file = package_dir / 'setup.py' + pyproject_file = package_dir / 'pyproject.toml' target_dir = tmp_path / 'tgt' package_dir.mkdir() - setup_file.touch() + pyproject_file.touch() target_dir.mkdir() mock_popen.return_value.communicate.return_value = ('output', '') @@ -177,7 +180,7 @@ def test_build_wheel_target_dir(mock_popen, tmp_path): target_dir=target_dir.as_posix()) expected_args = [ - sys.executable, 'setup.py', 'bdist_wheel', '-d', + sys.executable, '-m', 'pip', 'wheel', '--no-deps', '.', '-w', target_dir.as_posix() ] mock_popen.assert_called_once_with(expected_args,