1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301
|
# Contributing to azure-planetarycomputer
This guide covers the manual steps required after regenerating the SDK from TypeSpec, and how to validate your changes locally before pushing to CI.
For general Azure SDK for Python contribution guidance, see the [top-level CONTRIBUTING.md](https://github.com/Azure/azure-sdk-for-python/blob/main/CONTRIBUTING.md).
---
## Regenerating the SDK from TypeSpec
### 1. Update `tsp-location.yaml`
Edit `tsp-location.yaml` to point to the desired spec commit and directory:
```yaml
directory: specification/orbital/Microsoft.PlanetaryComputer
commit: <new-commit-sha>
repo: Azure/azure-rest-api-specs
```
### 2. Run code generation
From the package root (`sdk/planetarycomputer/azure-planetarycomputer`):
```bash
npx tsp-client update
```
### 3. Clean up generated artifacts
The code generator creates `generated_samples/` and `generated_tests/` directories that should **not** be committed. Delete them:
```bash
rm -rf generated_samples/ generated_tests/
```
### 4. Restore hand-written tests and samples
The generator may overwrite files in `tests/` and `samples/`. Restore them from Git:
```bash
git checkout -- tests/ samples/
```
---
## Known Manual Fixes on Generated Code
The TypeSpec code generator does not emit certain comments or formatting that CI checks require. The following fixes must be **manually applied** after every regeneration.
### Pylint Suppressions
The code generator already produces a file-level suppression on line 1 of each `_operations.py`:
`# pylint: disable=line-too-long,useless-suppression,too-many-lines`
> **Warning:** Do **not** extend line 1 beyond ~70 characters. The Azure pylint `file-needs-copyright-header` check (`C4726`) reads only the first 200 bytes of the file. If line 1 is too long, it pushes the copyright header past the 200-byte boundary and the check fails.
#### `azure/planetarycomputer/operations/_operations.py`
| Location | Suppression | Reason |
|---|---|---|
| `from collections.abc import MutableMapping` | `# pylint: disable=import-error` | Not resolvable in the pylint virtualenv |
| `from .._utils.model_base import (` | `# pylint: disable=unused-import` | `_deserialize_xml` is imported but not always used; the suppression must go on the `from` line (Black reformats the import into multi-line) |
| `def build_data_get_mosaics_tile_request(` | `# pylint: disable=too-many-locals,too-many-branches,too-many-statements` | This function exceeds pylint complexity thresholds; use inline suppression (not file-level to avoid making line 1 too long) |
#### `azure/planetarycomputer/aio/operations/_operations.py`
| Location | Suppression | Reason |
|---|---|---|
| `from collections.abc import MutableMapping` | `# pylint: disable=import-error` | Not resolvable in the pylint virtualenv |
| `from ..._utils.model_base import (` | `# pylint: disable=unused-import` | Same as sync - suppression goes on the `from` line |
#### `azure/planetarycomputer/_utils/model_base.py`
| Location | Suppression | Reason |
|---|---|---|
| `from collections.abc import MutableMapping` | `# pylint: disable=import-error` | Not resolvable in the pylint virtualenv |
| `return super().__new__(cls)` (in `Model.__new__`) | `# pylint: disable=no-value-for-parameter` | False positive - pylint cannot resolve the MRO for `__new__` |
> **Critical: Run Black _before_ adding pylint suppressions.** Black reformats imports (turning single-line into multi-line), which changes where `# pylint: disable` comments must go. If you add suppressions first and then run Black, the comments may end up on the wrong line. The correct order is: (1) run `tox -e black`, (2) restore tests/samples, (3) add pylint suppressions.
> **Emitter version variability:** The emitter may add different inline suppressions to different functions. For example, `build_data_get_mosaics_tile_json_request` gets the full `too-many-locals,too-many-branches,too-many-statements`, while `build_data_get_mosaics_tile_request` only gets `too-many-locals`. Always check the pylint output to see if additional suppressions are needed beyond what the emitter provides.
### Sphinx Docstring Fixes
The code generator sometimes produces RST formatting bugs in docstrings (e.g., code block terminators merged with following text, incorrect bullet continuation indentation). These must be fixed in **both** sync and async `_operations.py`. Run `tox -e sphinx` after each regeneration — Sphinx treats warnings as errors, so any formatting issues will cause a build failure.
### Sample and Test Updates
If the TypeSpec renames or removes API operations, the hand-written samples under `samples/` and `samples/async/` must be updated to match. MyPy and Pyright (which also check samples) will catch these as type errors.
**Test updates require special care:**
- **Do NOT rename test methods** even if the SDK method they test was renamed. Test method names determine recording file paths. Renaming a test method breaks the recording lookup — only change the API call *inside* the test body.
- **Update type assertions** if an API's return type changes (e.g., from `dict` to a typed model). The test assertions must match the new return type.
- **Run tests in playback mode** (`pytest tests/ -v`) after all fixes to verify all tests pass.
---
## Local Validation
Run the following checks **from the package root** before pushing. All commands use the shared tox config:
```bash
cd sdk/planetarycomputer/azure-planetarycomputer
```
### Formatting (Black)
```bash
tox -e black -c ../../../eng/tox/tox.ini --root .
```
### Linting (Pylint)
```bash
tox -e pylint -c ../../../eng/tox/tox.ini --root .
```
### Type Checking (MyPy)
```bash
tox -e mypy -c ../../../eng/tox/tox.ini --root .
```
### Type Checking (Pyright)
```bash
tox -e pyright -c ../../../eng/tox/tox.ini --root .
```
### Documentation (Sphinx)
```bash
tox -e sphinx -c ../../../eng/tox/tox.ini --root .
```
### API Stub Generation
```bash
tox -e apistub -c ../../../eng/tox/tox.ini --root .
```
> **Tip:** Running `black`, `pylint`, `mypy`, `pyright`, and `sphinx` locally catches the vast majority of CI failures before you push.
---
## Testing
This package uses the [Azure SDK test proxy](https://github.com/Azure/azure-sdk-tools/blob/main/tools/test-proxy/Azure.Sdk.Tools.TestProxy/README.md) with recorded HTTP sessions stored in the [azure-sdk-assets](https://github.com/Azure/azure-sdk-assets) repo. All commands below assume you are in the package root:
```bash
cd sdk/planetarycomputer/azure-planetarycomputer
```
### Environment setup
Create a `.env` file in the package root (this file is gitignored) with your live resource credentials:
```dotenv
PLANETARYCOMPUTER_ENDPOINT=https://<your-geocatalog>.geocatalog.spatio.azure.com
PLANETARYCOMPUTER_SUBSCRIPTION_ID=<subscription-id>
PLANETARYCOMPUTER_TENANT_ID=<tenant-id>
PLANETARYCOMPUTER_CLIENT_ID=<client-id>
PLANETARYCOMPUTER_CLIENT_SECRET=<client-secret>
PLANETARYCOMPUTER_COLLECTION_ID=<collection-id>
PLANETARYCOMPUTER_ITEM_ID=<item-id>
```
The `conftest.py` calls `load_dotenv()` so these are loaded automatically by pytest.
### Restoring recordings (first time / after pulling changes)
Before running tests in playback mode, you need to restore the recorded sessions from the assets repo. The test proxy does this automatically on the first playback run, but you can also restore explicitly:
```bash
test-proxy restore -a assets.json
```
This clones the tag referenced in `assets.json` into `.assets/` (a gitignored directory).
### Running tests in playback mode
Playback mode replays recorded HTTP sessions — no live Azure resources needed:
```bash
# PowerShell
$env:AZURE_TEST_RUN_LIVE="false"
pytest tests/ -v
# Bash
AZURE_TEST_RUN_LIVE=false pytest tests/ -v
```
You can also run a single test file:
```bash
pytest tests/test_planetary_computer_00_stac_collection.py -v
```
### Recording tests (live mode)
Recording mode runs tests against live Azure resources and captures all HTTP traffic for future playback. **Requires** the `.env` file to be set up with valid credentials.
```bash
# PowerShell
$env:AZURE_TEST_RUN_LIVE="true"
pytest tests/ -v
# Bash
AZURE_TEST_RUN_LIVE=true pytest tests/ -v
```
You can re-record individual test files:
```bash
$env:AZURE_TEST_RUN_LIVE="true"
pytest tests/test_planetary_computer_03_sas.py -v
pytest tests/test_planetary_computer_03_sas_async.py -v
```
> **Tip:** Always re-record **both** sync and async variants of a test file — they have separate recording files.
> **Tip:** After changing sanitizers in `conftest.py`, **all 16 test files** (8 sync + 8 async) must be re-recorded to ensure consistent sanitization across all recordings.
### Pushing recordings to the assets repo
After recording, the new sessions are stored locally in `.assets/`. Push them to the shared assets repo:
```bash
test-proxy push -a assets.json
```
This creates a new tag in `Azure/azure-sdk-assets` and **updates `assets.json` with the new tag automatically**. Verify the new tag:
```bash
cat assets.json
```
> **Important:** Commit the updated `assets.json` alongside your code changes so CI can find the correct recordings.
### Verifying playback after push
Always verify that playback still works after pushing recordings:
```bash
$env:AZURE_TEST_RUN_LIVE="false"
pytest tests/ -v
```
Expected result: **202 passed, 0 failed, 0 skipped**.
### Test file structure
| File | Tests | Description |
|---|---|---|
| `test_00_stac_collection` | 25 | STAC collection CRUD, thumbnail asset creation, metadata |
| `test_01_stac_items` | 19 | STAC item CRUD and querying |
| `test_02_stac_specification` | 13 | STAC API spec compliance (conformance, queryables) |
| `test_03_sas` | 5 | SAS token operations (get, sign, revoke, download) |
| `test_04_stac_item_tiler` | 19 | Tile rendering for individual STAC items |
| `test_05_mosaics_tiler` | 9 | Mosaic tile rendering and static images |
| `test_06_map_legends` | 5 | Map legend generation (continuous + classified) |
| `test_07_collection_lifecycle` | 6 | Full collection create → replace → delete lifecycle |
Each file has a sync and async variant (e.g., `test_00_stac_collection.py` and `test_00_stac_collection_async.py`).
### Key tips
- **Test ordering matters.** Tests within each file are numbered and run in order. For example, `test_10a_create_thumbnail_asset` in `test_00` creates the thumbnail that `test_11_get_collection_thumbnail` reads. Do not reorder or skip tests.
- **Sanitizers are session-scoped.** All sanitizers in `conftest.py` apply to every test. If you add or change a sanitizer, all recordings become stale and must be re-recorded.
- **`EnvironmentVariableLoader` interacts with sanitizers.** During playback, the `EnvironmentVariableLoader` (`PlanetaryComputerPreparer`) replaces real env var values with defaults (e.g., `naip-atl-2` → `naip-atl`). This happens *before* sanitizers run on the recording, so sanitizers must handle both original and post-replacement forms. See the secondary hash sanitizer in `conftest.py` for an example.
- **The `.assets/` directory is gitignored.** Never commit it. It's managed entirely by `test-proxy`.
- **SAS download timing.** `test_03`'s `test_04_signed_href_can_download_asset` uses `urlopen` to download via SAS URL. This occasionally gets 403 in live mode due to SAS delegation key timing when running in a full suite. It has retry logic (5 attempts, 15s delay). The download is skipped in playback mode, so it always passes in CI.
- **Default sanitizer removals.** `conftest.py` removes default Azure SDK sanitizers `AZSDK3493`, `AZSDK3430`, and `AZSDK2003` because collection IDs and item IDs are public STAC data, not secrets.
---
## Quick-Reference Checklist
After running `npx tsp-client update`:
- [ ] Delete `generated_samples/` and `generated_tests/`
- [ ] Restore `tests/` and `samples/` — `git checkout -- tests/ samples/`
- [ ] Run `tox -e black` — formatting (MUST be done before adding pylint suppressions)
- [ ] Restore `tests/` and `samples/` again if Black modified them
- [ ] Add inline `# pylint: disable=import-error` to `MutableMapping` imports (3 files)
- [ ] Add `# pylint: disable=unused-import` on the `from ... import (` line for `_deserialize_xml` (2 files)
- [ ] Add `# pylint: disable=too-many-locals,too-many-branches,too-many-statements` inline on `build_data_get_mosaics_tile_*` functions (check emitter output — may need to add missing suppressions)
- [ ] Add inline `# pylint: disable=no-value-for-parameter` to `Model.__new__` in `model_base.py`
- [ ] Fix any Sphinx docstring issues (both sync and async `_operations.py`) — check `tox -e sphinx` output
- [ ] Run `tox -e pylint` — linting (should score 10.00/10)
- [ ] Run `tox -e sphinx` — documentation (should build with 0 warnings)
- [ ] Run `tox -e mypy` and `tox -e pyright` — type checking (will catch renamed/removed APIs in samples)
- [ ] Update samples if any operations were renamed or removed
- [ ] Update tests: fix API calls (not method names!), update `isinstance` checks for changed return types
- [ ] Run `pytest tests/ -v` in playback mode — should be 202 passed
- [ ] Run `tox -e apistub` — API stub generation
- [ ] Re-record tests if HTTP request/response data changed (see Testing section)
- [ ] Update `CHANGELOG.md` with a release date if preparing a release
|