Repository: planetlabs/planet-client-python

Issue:
update SDK to latest product bundle spec
A new product bundle was released 2/22/2022. Update the SDK to use the new spec.

Discussion:

Patch:
diff --git a/data/README.md b/data/README.md
index c07dc5c..66af4c6 100644
--- a/data/README.md
+++ b/data/README.md
@@ -1,7 +1,7 @@
 # data 
 This directory contains static external resources used by the codebase.
 
-## orders_product_bundle_2020_03_10.json
+## orders_product_bundle_2022_02_02.json
 This file was downloaded from the Planet
 [Product Bundles Reference](https://developers.planet.com/docs/orders/product-bundles-reference/)
 page and is stored without any adjustments.
diff --git a/data/orders_product_bundle_2020_03_10.json b/data/orders_product_bundle_2022_02_02.json
similarity index 70%
rename from data/orders_product_bundle_2020_03_10.json
rename to data/orders_product_bundle_2022_02_02.json
index 3a20f30..ccc8bfd 100644
--- a/data/orders_product_bundle_2020_03_10.json
+++ b/data/orders_product_bundle_2022_02_02.json
@@ -1,8 +1,8 @@
 {
   "bundles": {
     "analytic": {
-      "name": "Analytic Radiance",
-      "description": "Orthorectified product, calibrated to at-sensor radiance",
+      "name": "Analytic Radiance (TOAR)",
+      "description": "Calibrated to top of atmosphere radiance",
       "bands": "all",
       "rectification": "orthorectified",
       "radiometry": "at-sensor",
@@ -75,13 +75,13 @@
       }
     },
     "analytic_udm2": {
-      "name": "Analytic Radiance with UDM2",
-      "description": "Orthorectified product, calibrated to at-sensor radiance. Includes udm2.",
+      "name": "Analytic Radiance (TOAR)",
+      "description": "Calibrated to top of atmosphere radiance",
       "bands": "all",
       "rectification": "orthorectified",
       "radiometry": "at-sensor",
       "fileType": "GeoTIFF",
-      "auxiliaryFiles": "udm and udm2",
+      "auxiliaryFiles": "udm, udm2",
       "assets": {
         "PSOrthoTile": [
           "analytic",
@@ -95,6 +95,11 @@
           "udm",
           "udm2"
         ],
+        "PSScene": [
+          "ortho_analytic_4b",
+          "ortho_analytic_4b_xml",
+          "ortho_udm2"
+        ],
         "SkySatScene": [
           "ortho_analytic",
           "ortho_analytic_udm",
@@ -107,9 +112,25 @@
         ]
       }
     },
+    "analytic_3b_udm2": {
+      "name": "Analytic Radiance (TOAR) 3b",
+      "description": "Calibrated to top of atmosphere radiance, 3 band",
+      "bands": "3-band",
+      "rectification": "orthorectified",
+      "radiometry": "at-sensor",
+      "fileType": "GeoTIFF",
+      "auxiliaryFiles": "udm2",
+      "assets": {
+        "PSScene": [
+          "ortho_analytic_3b",
+          "ortho_analytic_3b_xml",
+          "ortho_udm2"
+        ]
+      }
+    },
     "analytic_5b": {
-      "name": "Analytic Image",
-      "description": "Radiometrically-calibrated analytic image stored as 16-bit scaled radiance",
+      "name": "Analytic Radiance (TOAR) 5b",
+      "description": "Calibrated to top of atmosphere radiance, 5 band",
       "bands": "all",
       "rectification": "orthorectified",
       "radiometry": "at-sensor",
@@ -124,13 +145,13 @@
       }
     },
     "analytic_5b_udm2": {
-      "name": "Analytic Image",
-      "description": "Radiometrically-calibrated analytic image stored as 16-bit scaled radiance. Includes udm2",
+      "name": "Analytic Radiance (TOAR) 5b",
+      "description": "Calibrated to top of atmosphere radiance, 5 band",
       "bands": "all",
       "rectification": "orthorectified",
       "radiometry": "at-sensor",
       "fileType": "GeoTIFF",
-      "auxiliaryFiles": "udm and udm2",
+      "auxiliaryFiles": "udm, udm2",
       "assets": {
         "PSOrthoTile": [
           "analytic_5b",
@@ -140,9 +161,25 @@
         ]
       }
     },
+    "analytic_8b_udm2": {
+      "name": "Analytic Radiance (TOAR) 8b",
+      "description": "Calibrated to top of atmosphere radiance, 8 band",
+      "bands": "8-band",
+      "rectification": "orthorectified",
+      "radiometry": "at-sensor",
+      "fileType": "GeoTIFF",
+      "auxiliaryFiles": "udm2",
+      "assets": {
+        "PSScene": [
+          "ortho_analytic_8b",
+          "ortho_analytic_8b_xml",
+          "ortho_udm2"
+        ]
+      }
+    },
     "visual": {
       "name": "Visual",
-      "description": "Orthorectified product, visually enhanced for optimal appearance",
+      "description": "RGB only -- color corrected and optimized for visual analysis",
       "bands": "3-band",
       "rectification": "orthorectified",
       "radiometry": "visual",
@@ -159,6 +196,9 @@
           "visual",
           "visual_xml"
         ],
+        "PSScene": [
+          "ortho_visual"
+        ],
         "REOrthoTile": [
           "visual",
           "visual_xml"
@@ -176,7 +216,7 @@
     },
     "uncalibrated_dn": {
       "name": "Uncalibrated DN",
-      "description": "Orthorectified product, non-radiometrically-calibrated, raw digital number",
+      "description": "Uncalibrated digital numbers, suitable for custom radiometric processing",
       "bands": "all",
       "rectification": "orthorectified",
       "radiometry": "digital numbers",
@@ -209,13 +249,13 @@
       }
     },
     "uncalibrated_dn_udm2": {
-      "name": "Uncalibrated DN with UDM2",
-      "description": "Orthorectified product, non-radiometrically-calibrated, raw digital number. Includes udm2.",
+      "name": "Uncalibrated DN",
+      "description": "Uncalibrated digital numbers, suitable for custom radiometric processing",
       "bands": "all",
       "rectification": "orthorectified",
       "radiometry": "digital numbers",
       "fileType": "GeoTIFF",
-      "auxiliaryFiles": "udm and udm2",
+      "auxiliaryFiles": "udm, udm2",
       "assets": {
         "PSOrthoTile": [
           "analytic_dn",
@@ -242,8 +282,8 @@
       }
     },
     "basic_analytic": {
-      "name": "Non-Orthorectified Analytic Radiance",
-      "description": "Non-orthorectified, calibrated to at-sensor radiance",
+      "name": "Basic Analytic Radiance (TOAR)",
+      "description": "Calibrated to top of atmosphere radiance - includes RPC text file for georeferencing",
       "bands": "all",
       "rectification": "non-orthorectified",
       "radiometry": "at-sensor",
@@ -282,13 +322,13 @@
       }
     },
     "basic_analytic_udm2": {
-      "name": "Non-Orthorectified Analytic Radiance with UDM2",
-      "description": "Non-orthorectified, calibrated to at-sensor radiance. Includes udm2.",
+      "name": "Basic Analytic Radiance (TOAR)",
+      "description": "Calibrated to top of atmosphere radiance - includes RPC text file for georeferencing",
       "bands": "all",
       "rectification": "non-orthorectified",
       "radiometry": "at-sensor",
       "fileType": "TIFF and RPCs",
-      "auxiliaryFiles": "udm and udm2",
+      "auxiliaryFiles": "udm, udm2",
       "assets": {
         "PSScene4Band": [
           "basic_analytic",
@@ -297,6 +337,12 @@
           "basic_analytic_rpc",
           "basic_analytic_xml"
         ],
+        "PSScene": [
+          "basic_analytic_4b",
+          "basic_udm2",
+          "basic_analytic_4b_rpc",
+          "basic_analytic_4b_xml"
+        ],
         "SkySatScene": [
           "basic_analytic",
           "basic_analytic_rpc",
@@ -304,9 +350,26 @@
         ]
       }
     },
+    "basic_analytic_8b_udm2": {
+      "name": "Basic Analytic Radiance (TOAR) 8b",
+      "description": "Calibrated to top of atmosphere radiance - includes RPC text file for georeferencing",
+      "bands": "8-band",
+      "rectification": "non-orthorectified",
+      "radiometry": "at-sensor",
+      "fileType": "TIFF and RPCs",
+      "auxiliaryFiles": "udm2",
+      "assets": {
+        "PSScene": [
+          "basic_analytic_8b",
+          "basic_udm2",
+          "basic_analytic_4b_rpc",
+          "basic_analytic_8b_xml"
+        ]
+      }
+    },
     "basic_uncalibrated_dn": {
-      "name": "Non-Orthorectified, Uncalibrated DN",
-      "description": "Non-orthorectified, non-radiometrically-calibrated, raw digital number",
+      "name": "Basic Uncalibrated DN",
+      "description": "Uncalibrated digital numbers - includes RPC text file for georeferencing",
       "bands": "all",
       "rectification": "non-orthorectified",
       "radiometry": "digital numbers",
@@ -333,13 +396,13 @@
       }
     },
     "basic_uncalibrated_dn_udm2": {
-      "name": "Non-Orthorectified, Uncalibrated DN with UDM2",
-      "description": "Non-orthorectified, non-radiometrically-calibrated, raw digital number. Includes udm2.",
+      "name": "Basic Uncalibrated DN",
+      "description": "Uncalibrated digital numbers - includes RPC text file for georeferencing",
       "bands": "all",
       "rectification": "non-orthorectified",
       "radiometry": "digital numbers",
       "fileType": "TIFF and RPCs",
-      "auxiliaryFiles": "udm and udm2",
+      "auxiliaryFiles": "udm, udm2",
       "assets": {
         "PSScene4Band": [
           "basic_analytic_dn",
@@ -356,8 +419,8 @@
       }
     },
     "analytic_sr": {
-      "name": "Analytic Surface Reflectance",
-      "description": "Orthorectified product, radiometrically calibrated and atmospherically corrected to surface reflectance",
+      "name": "Surface Reflectance",
+      "description": "Corrected for surface reflectance – recommended for most analytic applications",
       "bands": "all",
       "rectification": "orthorectified",
       "radiometry": "surface reflectance",
@@ -446,17 +509,25 @@
           "analytic_obscov_500m",
           "analytic_iobs_res",
           "analytic_q_scan"
+        ],
+        "SkySatCollect": [
+          "ortho_analytic_sr",
+          "ortho_analytic_udm"
+        ],
+        "SkySatScene": [
+          "ortho_analytic_sr",
+          "ortho_analytic_udm"
         ]
       }
     },
     "analytic_sr_udm2": {
-      "name": "Analytic Surface Reflectance with UDM2",
-      "description": "Orthorectified product, radiometrically calibrated and atmospherically corrected to surface reflectance. Includes udm2.",
+      "name": "Surface Reflectance 4b",
+      "description": "Corrected for surface reflectance – recommended for most analytic applications, 4 band",
       "bands": "all",
       "rectification": "orthorectified",
       "radiometry": "surface reflectance",
       "fileType": "GeoTIFF",
-      "auxiliaryFiles": "udm and udm2",
+      "auxiliaryFiles": "udm, udm2",
       "assets": {
         "PSScene4Band": [
           "analytic_sr",
@@ -464,17 +535,48 @@
           "udm",
           "udm2"
         ],
+        "PSScene": [
+          "ortho_analytic_4b_sr",
+          "ortho_analytic_4b_xml",
+          "ortho_udm2"
+        ],
         "PSOrthoTile": [
           "analytic_sr",
           "udm",
           "udm2",
           "analytic_xml"
+        ],
+        "SkySatCollect": [
+          "ortho_analytic_sr",
+          "ortho_analytic_udm",
+          "ortho_analytic_udm2"
+        ],
+        "SkySatScene": [
+          "ortho_analytic_sr",
+          "ortho_analytic_udm",
+          "ortho_analytic_udm2"
+        ]
+      }
+    },
+    "analytic_8b_sr_udm2": {
+      "name": "Surface Reflectance 8b",
+      "description": "Corrected for surface reflectance – recommended for most analytic applications, 8 band",
+      "bands": "8-band",
+      "rectification": "orthorectified",
+      "radiometry": "surface reflectance",
+      "fileType": "GeoTIFF",
+      "auxiliaryFiles": "udm2",
+      "assets": {
+        "PSScene": [
+          "ortho_analytic_8b_sr",
+          "ortho_analytic_8b_xml",
+          "ortho_udm2"
         ]
       }
     },
     "basic_uncalibrated_dn_nitf": {
-      "name": "Non-Orthorectified, Uncalibrated DN NITF",
-      "description": "Non-orthorectified, non-radiometrically-calibrated, raw digital number in NITF format",
+      "name": "Basic Uncalibrated DN - NITF",
+      "description": "Uncalibrated digital numbers - includes RPC text file for georeferencing",
       "bands": "all",
       "rectification": "non-orthorectified",
       "radiometry": "digital numbers",
@@ -490,13 +592,13 @@
       }
     },
     "basic_uncalibrated_dn_nitf_udm2": {
-      "name": "Non-Orthorectified, Uncalibrated DN NITF with UDM2",
-      "description": "Non-orthorectified, non-radiometrically-calibrated, raw digital number in NITF format. Includes udm2.",
+      "name": "Basic Uncalibrated DN - NITF",
+      "description": "Uncalibrated digital numbers - includes RPC text file for georeferencing",
       "bands": "all",
       "rectification": "non-orthorectified",
       "radiometry": "digital numbers",
       "fileType": "NITF",
-      "auxiliaryFiles": "udm and udm2",
+      "auxiliaryFiles": "udm, udm2",
       "assets": {
         "PSScene4Band": [
           "basic_analytic_dn_nitf",
@@ -508,8 +610,8 @@
       }
     },
     "basic_analytic_nitf": {
-      "name": "Non-Orthorectified Analytic Radiance NITF",
-      "description": "Non-orthorectified, calibrated to at-sensor radiance, in NITF format",
+      "name": "Basic Analytic Radiance (TOAR) – NITF",
+      "description": " Calibrated to top of atmosphere radiance - includes RPC text file for georeferencing",
       "bands": "all",
       "rectification": "non-orthorectified",
       "radiometry": "at-sensor",
@@ -537,13 +639,13 @@
       }
     },
     "basic_analytic_nitf_udm2": {
-      "name": "Non-Orthorectified Analytic Radiance NITF with UDM2",
-      "description": "Non-orthorectified, calibrated to at-sensor radiance, in NITF format. Includes udm2.",
+      "name": "Basic Analytic Radiance (TOAR) – NITF",
+      "description": " Calibrated to top of atmosphere radiance - includes RPC text file for georeferencing",
       "bands": "all",
       "rectification": "non-orthorectified",
       "radiometry": "at-sensor",
       "fileType": "NITF",
-      "auxiliaryFiles": "udm and udm2",
+      "auxiliaryFiles": "udm, udm2",
       "assets": {
         "PSScene4Band": [
           "basic_analytic_nitf",
@@ -555,8 +657,8 @@
       }
     },
     "basic_panchromatic": {
-      "name": "Non-orthorectified, scaled radiance image",
-      "description": "Non-orthorectified radiometrically-calibrated panchromatic imagery",
+      "name": "Basic Panchromatic",
+      "description": "Unrectified panchromatic band - includes RPC text file for georeferencing",
       "bands": "panchromatic",
       "rectification": "non-orthorectified",
       "radiometry": "at-sensor",
@@ -564,13 +666,14 @@
       "assets": {
         "SkySatScene": [
           "basic_panchromatic",
-          "basic_panchromatic_rpc"
+          "basic_panchromatic_rpc",
+          "basic_panchromatic_udm2"
         ]
       }
     },
     "basic_panchromatic_dn": {
-      "name": "Non-orthorectified panchromatic imagery, non-radiometrically calibrated, raw digital number",
-      "description": "Unorthorectified panchromatic imagery, non-radiometrically calibrated, raw digital number",
+      "name": "Basic Panchromatic DN",
+      "description": "Unrectified, panchromatic band, uncalibrated digital numbers - includes RPC text file for georeferencing",
       "bands": "panchromatic",
       "rectification": "non-orthorectified",
       "radiometry": "digital numbers",
@@ -578,13 +681,14 @@
       "assets": {
         "SkySatScene": [
           "basic_panchromatic_dn",
-          "basic_panchromatic_dn_rpc"
+          "basic_panchromatic_dn_rpc",
+          "basic_panchromatic_udm2"
         ]
       }
     },
     "panchromatic": {
       "name": "Panchromatic",
-      "description": "Orthorectified radiometrically-calibrated panchromatic image stored as 16-bit scaled radiance",
+      "description": "Panchromatic band, calibrated to top of atmosphere radiance",
       "bands": "panchromatic",
       "rectification": "orthorectified",
       "radiometry": "at-sensor",
@@ -603,7 +707,7 @@
     },
     "panchromatic_dn": {
       "name": "Panchromatic DN",
-      "description": "Orthorectified panchromatic imagery, non-radiometrically calibrated, raw digital number",
+      "description": "Uncalibrated panchromatic band - suitable for custom radiometric processing",
       "bands": "panchromatic",
       "rectification": "orthorectified",
       "radiometry": "digital numbers",
@@ -621,8 +725,8 @@
       }
     },
     "panchromatic_dn_udm2": {
-      "name": "Panchromatic DN with UDM2",
-      "description": "Orthorectified panchromatic imagery, non-radiometrically calibrated, raw digital number",
+      "name": "Panchromatic DN",
+      "description": "Uncalibrated panchromatic band - suitable for custom radiometric processing",
       "bands": "panchromatic",
       "rectification": "orthorectified",
       "radiometry": "digital numbers",
@@ -642,8 +746,8 @@
       }
     },
     "pansharpened": {
-      "name": "Pansharpened",
-      "description": "Orthorectifed, pan-sharpened imagery, non-radiometrically calibrated, raw digital number",
+      "name": "Ortho-pansharpened",
+      "description": "Pansharpened, color corrected, 4-band multispectral data",
       "bands": "all",
       "rectification": "orthorectified",
       "radiometry": "digital numbers",
@@ -662,8 +766,8 @@
       }
     },
     "pansharpened_udm2": {
-      "name": "Pansharpened with UDM2",
-      "description": "Orthorectifed, pan-sharpened imagery, non-radiometrically calibrated, raw digital number",
+      "name": "Ortho-pansharpened",
+      "description": "Pansharpened, color corrected, 4-band multispectral data",
       "bands": "all",
       "rectification": "orthorectified",
       "radiometry": "digital numbers",
@@ -684,8 +788,8 @@
       }
     },
     "basic_l1a_dn": {
-      "name": "Non-Orthorectified, Level 1A Uncalibrated DN",
-      "description": "Non-orthorectified, non-radiometrically-calibrated, raw digital number, Level 1A",
+      "name": "Basic L1A",
+      "description": "Unrectified, uncalibrated panchromatic band only, no super resolution applied - includes RPC text file for georeferencing",
       "bands": "panchromatic",
       "rectification": "non-orthorectified",
       "radiometry": "digital numbers",
@@ -694,9 +798,12 @@
         "SkySatScene": [
           "basic_l1a_panchromatic_dn",
           "basic_l1a_panchromatic_dn_rpc"
+        ],
+        "SkySatCollect": [
+          "basic_l1a_all_frames"
         ]
       }
     }
   },
-  "version": "2020-03-10"
+  "version": "2022-02-02"
 }
diff --git a/planet/specs.py b/planet/specs.py
index 874d52b..8fa68f1 100644
--- a/planet/specs.py
+++ b/planet/specs.py
@@ -1,4 +1,4 @@
-# Copyright 2020 Planet Labs, Inc.
+# Copyright 2020 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.
@@ -18,7 +18,7 @@ import os
 from pathlib import Path
 
 DATA_DIR = 'data'
-PRODUCT_BUNDLE_SPEC_NAME = 'orders_product_bundle_2020_03_10.json'
+PRODUCT_BUNDLE_SPEC_NAME = 'orders_product_bundle_2022_02_02.json'
 SUPPORTED_TOOLS = [
     'band_math',
     'clip',
@@ -47,11 +47,10 @@ def validate_bundle(bundle):
     return _validate_field(bundle, supported, 'product_bundle')
 
 
-def validate_item_type(item_type, bundle, report_field_name=True):
+def validate_item_type(item_type, bundle):
     bundle = validate_bundle(bundle)
     supported = get_item_types(bundle)
-    field_name = 'item_type' if report_field_name else None
-    return _validate_field(item_type, supported, field_name)
+    return _validate_field(item_type, supported, 'item_type')
 
 
 def validate_order_type(order_type):
@@ -72,14 +71,12 @@ def validate_file_format(file_format):
     return _validate_field(file_format, SUPPORTED_FILE_FORMATS, 'file_format')
 
 
-def _validate_field(value, supported, field_name=None):
+def _validate_field(value, supported, field_name):
     try:
         value = get_match(value, supported)
     except (NoMatchException):
         opts = ', '.join(["'" + s + "'" for s in supported])
-        msg = f'\'{value}\' is not one of {opts}.'
-        if field_name:
-            msg = f'{field_name} - ' + msg
+        msg = f'{field_name} - \'{value}\' is not one of {opts}.'
         raise SpecificationException(msg)
     return value
 
diff --git a/setup.cfg b/setup.cfg
index f51bc17..6a90e35 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -10,7 +10,7 @@ addopts =
     --cov=tests
     --cov-report=term-missing
     --cov-report=xml
-    --cov-fail-under 97
+    --cov-fail-under 98
     -rxXs
 
 [coverage:run]


Test patch:
diff --git a/tests/unit/test_specs.py b/tests/unit/test_specs.py
index a795a95..01ff355 100644
--- a/tests/unit/test_specs.py
+++ b/tests/unit/test_specs.py
@@ -1,4 +1,4 @@
-# Copyright 2020 Planet Labs, Inc.
+# Copyright 2020 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.
@@ -19,9 +19,9 @@ from planet import specs
 
 LOGGER = logging.getLogger(__name__)
 
-TEST_PRODUCT_BUNDLE = 'analytic'
-
-TEST_ITEM_TYPE = 'PSOrthoTile'
+TEST_PRODUCT_BUNDLE = 'visual'
+# must be a valid item type for TEST_PRODUCT_BUNDLE
+TEST_ITEM_TYPE = 'PSScene'
 
 
 def test_get_type_match():
@@ -34,39 +34,52 @@ def test_get_type_match():
         specs.get_match('a', ['b'])
 
 
-def test_validate_bundle():
+def test_validate_bundle_supported():
     assert 'analytic' == specs.validate_bundle('ANALYTIC')
 
+
+def test_validate_bundle_notsupported():
     with pytest.raises(specs.SpecificationException):
         specs.validate_bundle('notsupported')
 
 
-def test_validate_item_type():
+def test_validate_item_type_supported():
     assert 'PSOrthoTile' == specs.validate_item_type('psorthotile', 'analytic')
 
+
+def test_validate_item_type_notsupported_bundle():
     with pytest.raises(specs.SpecificationException):
         specs.validate_item_type('psorthotile', 'wha')
 
+
+def test_validate_item_type_notsupported_itemtype():
+    with pytest.raises(specs.SpecificationException):
         specs.validate_item_type('notsupported', 'analytic')
 
 
-def test_validate_order_type():
+def test_validate_order_type_supported():
     assert 'full' == specs.validate_order_type('FULL')
 
+
+def test_validate_order_type_notsupported():
     with pytest.raises(specs.SpecificationException):
         specs.validate_order_type('notsupported')
 
 
-def test_validate_arhive_type():
+def test_validate_arhive_type_supported():
     assert 'zip' == specs.validate_archive_type('ZIP')
 
+
+def test_validate_arhive_type_notsupported():
     with pytest.raises(specs.SpecificationException):
         specs.validate_archive_type('notsupported')
 
 
-def test_validate_file_format():
+def test_validate_file_format_supported():
     assert 'COG' == specs.validate_file_format('cog')
 
+
+def test_validate_file_format_notsupported():
     with pytest.raises(specs.SpecificationException):
         specs.validate_archive_type('notsupported')