Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,28 @@ jobs:
pip install --upgrade nox
nox -s analyze

validate-models:
name: Validate Models Against Live Specs
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: 3.12
- name: Pip cache
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip
restore-keys: |
${{ runner.os }}-pip
- name: Validate models
run: |
pip install --upgrade nox
nox -s validate_models

test:
name: Test Python ${{ matrix.python-version }}
runs-on: ubuntu-latest
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,13 @@ The Planet SDK for Python is [hosted on PyPI](https://pypi.org/project/planet/)
pip install planet
```

For optional typed request and response models, generated from Planet's OpenAPI
specs, install the `models` extra. It adds a `pydantic` dependency:

```console
pip install planet[models]
```

To install from source, first clone this repository, then navigate to the root directory (where `setup.py` lives) and run:

```console
Expand Down
73 changes: 70 additions & 3 deletions noxfile.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
import json
from pathlib import Path
import shutil
import sys
import tempfile

import nox

sys.path.insert(0, str(Path(__file__).parent / "scripts"))
import type_gen # noqa: E402

nox.options.stop_on_first_error = True
nox.options.reuse_existing_virtualenvs = False

nox.options.sessions = ['lint', 'analyze', 'test', 'coverage', 'docs']

source_files = ("planet", "examples", "tests", "setup.py", "noxfile.py")
# Generated code — excluded from linting and formatting checks
generated_dirs = ("planet/types", )

BUILD_DIRS = ['build', 'dist']

Expand All @@ -17,7 +25,11 @@
def analyze(session):
session.install(".[lint]")

session.run("mypy", "--ignore-missing", "planet")
session.run("mypy",
"--ignore-missing",
"--exclude",
"|".join(generated_dirs),
"planet")


@nox.session
Expand Down Expand Up @@ -63,8 +75,13 @@ def test(session):
def lint(session):
session.install("-e", ".[lint]")

session.run("flake8", *source_files)
session.run('yapf', '--diff', '-r', *source_files)
session.run("flake8",
f"--extend-exclude={','.join(generated_dirs)}",
*source_files)
# yapf --exclude is a repeatable flag taking one fnmatch pattern; a bare
# directory name matches nothing, so the trailing /* is required.
yapf_excludes = [f"--exclude={d}/*" for d in generated_dirs]
session.run('yapf', '--diff', '-r', *yapf_excludes, *source_files)


@nox.session
Expand Down Expand Up @@ -114,6 +131,56 @@ def examples(session):
session.run('pytest', '--no-cov', 'examples/', '-s', *options)


@nox.session(python="3.12")
def generate_models(session):
"""Re-generate the Pydantic models in planet/types/ from the live specs.

Output must stay byte-identical to what `nox -s validate_models`
regenerates. Run after a spec change, then re-run validate_models.
"""
session.install("-e", ".[validate_models]")

for name, url in type_gen.SPECS.items():
output = type_gen.MODELS_DIR / f"{name}.py"
spec = type_gen.fetch_and_patch_spec(url)
with tempfile.NamedTemporaryFile(suffix=".json",
delete=False,
mode="w") as spec_tmp:
json.dump(spec, spec_tmp)
spec_path = Path(spec_tmp.name)
try:
session.run(*type_gen.codegen_argv(spec_path, output))
finally:
spec_path.unlink(missing_ok=True)


@nox.session(python="3.12")
def validate_models(session):
"""Validate committed Pydantic models match the live API specs.

Fetches live OpenAPI specs from Planet's API and compares against committed
snapshots. Fails if any spec has changed. No API key required.

To refresh snapshots after a deliberate API change, run:
nox -s generate_models
Runs in PR CI; not included in the default nox session list.
"""
session.install("-e", ".[validate_models]")
session.run(
"pytest",
"tests/drift/validate_models.py",
# Stop conftest discovery below tests/, whose conftest imports the
# full test-suite dependencies that this extra deliberately omits.
"--confcutdir=tests/drift",
# setup.cfg addopts injects --cov, but this extra deliberately omits
# pytest-cov; clear addopts rather than pull in the full test deps.
"-o",
"addopts=",
"-v",
"--tb=short",
)


@nox.session
def build(session):
"""Build package"""
Expand Down
4 changes: 2 additions & 2 deletions planet/cli/destinations.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ async def _set_default_destination(ctx, destination_id, pretty):
async def _unset_default_destination(ctx, pretty):
async with destinations_client(ctx) as cl:
try:
response = await cl.unset_default_destination()
echo_json(response, pretty)
await cl.unset_default_destination()
echo_json(None, pretty)
except Exception as e:
raise ClickException(f"Failed to unset default destination: {e}")

Expand Down
34 changes: 34 additions & 0 deletions planet/types/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Copyright 2026 Planet Labs PBC.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
"""Typed request and response models, generated from Planet's OpenAPI specs.

Requires pydantic, which is an optional dependency:

pip install planet[models]

The rest of the SDK does not import this package. Clients return plain dicts;
these models are opt-in validation on top of them:

from planet.types.destinations import Destination

dest = Destination.model_validate(client.get_destination(dest_id))

To regenerate after a spec change, run `nox -s generate_models`.
"""
try:
import pydantic as _pydantic # noqa: F401
except ImportError as exc: # pragma: no cover
raise ImportError(
"planet.types requires pydantic, which is not installed. "
"Install it with: pip install planet[models]") from exc
Loading
Loading