Skip to content

Commit cf6585d

Browse files
authored
Merge pull request #617 from ngageoint/integration/1.3.63
Release v1.3.63
2 parents efcc1e6 + 5ef4f38 commit cf6585d

49 files changed

Lines changed: 4959 additions & 407 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/build.yml

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -96,23 +96,23 @@ jobs:
9696
target_commitish: ${{github.ref}}
9797
files: dist/*
9898

99-
publish-to-pypi:
100-
name: Publish to PyPI
101-
needs: release
102-
runs-on: ubuntu-latest
103-
environment:
104-
name: development
105-
permissions:
106-
id-token: write
107-
steps:
108-
- name: Download all the dists
109-
uses: actions/download-artifact@v4
110-
with:
111-
name: ${{ needs.build.outputs.version }}
112-
path: dist
113-
merge-multiple: true
114-
- run: ls -R dist
115-
- name: Publish distribution to PyPI
116-
uses: pypa/gh-action-pypi-publish@release/v1
117-
with:
118-
repository-url: https://pypi.org/legacy/
99+
# publish-to-pypi:
100+
# name: Publish to PyPI
101+
# needs: release
102+
# runs-on: ubuntu-latest
103+
# environment:
104+
# name: development
105+
# permissions:
106+
# id-token: write
107+
# steps:
108+
# - name: Download all the dists
109+
# uses: actions/download-artifact@v4
110+
# with:
111+
# name: ${{ needs.build.outputs.version }}
112+
# path: dist
113+
# merge-multiple: true
114+
# - run: ls -R dist
115+
# - name: Publish distribution to PyPI
116+
# uses: pypa/gh-action-pypi-publish@release/v1
117+
# with:
118+
# repository-url: https://pypi.org/legacy/

.github/workflows/pypi.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,19 @@ jobs:
1313
uses: actions/setup-python@v5
1414
with:
1515
python-version: "3.x"
16+
- name: Extract version number from __about__.py
17+
shell: bash
18+
run: echo "raw_version=`sed -n 's/__version__ = \"\(.*\)\"/\1/p' < sarpy/__about__.py`" >> $GITHUB_OUTPUT
19+
id: extract_version
20+
- name: Set version number for release candidate
21+
shell: bash
22+
if: contains(github.ref, 'refs/heads/integration/')
23+
run: |
24+
echo ${{ steps.extract_version.outputs.raw_version }}
25+
rc_version=${{ steps.extract_version.outputs.raw_version }}rc1
26+
sed -i -e "s/__version__ = \"${{ steps.extract_version.outputs.raw_version }}/__version__ = \"$rc_version/g" sarpy/__about__.py
27+
echo "version=$rc_version" >> $GITHUB_OUTPUT
28+
id: extract_release_candidate_version
1629
- name: Install pypa/build
1730
run: >-
1831
python3 -m

sarpy/__about__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828

2929
from sarpy.__details__ import __classification__, _post_identifier
3030

31-
__version__ = "1.3.62"
31+
__version__ = "1.3.63"
3232

3333
__author__ = "National Geospatial-Intelligence Agency"
3434
__url__ = "https://github.com/ngageoint/sarpy"

sarpy/annotation/base.py

Lines changed: 95 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,12 @@
55
__classification__ = "UNCLASSIFIED"
66
__author__ = "Thomas McCullough"
77

8+
from collections import OrderedDict
9+
import json
810
import logging
911
import os
10-
from uuid import uuid4
1112
from typing import Optional, Dict, List, Any, Union
12-
import json
13-
14-
from collections import OrderedDict
13+
from uuid import uuid4
1514

1615
from sarpy.geometry.geometry_elements import Jsonable, FeatureCollection, Feature, \
1716
GeometryCollection, GeometryObject, Geometry, basic_assemble_from_collection
@@ -33,7 +32,7 @@ def __init__(self, uid=None, name=None, color=None):
3332
if uid is None:
3433
uid = str(uuid4())
3534
if not isinstance(uid, str):
36-
raise TypeError('uid must be a string')
35+
raise TypeError('uid must be a string, got {}'.format(type(uid)))
3736
self._uid = uid
3837

3938
self.name = name
@@ -60,7 +59,7 @@ def name(self, value):
6059
if value is None or isinstance(value, str):
6160
self._name = value
6261
else:
63-
raise TypeError('Got unexpected type for name')
62+
raise TypeError('Got unexpected type of {} for name'.format(type(value)))
6463

6564
@property
6665
def color(self):
@@ -75,7 +74,7 @@ def color(self, value):
7574
if value is None or isinstance(value, str):
7675
self._color = value
7776
else:
78-
raise TypeError('Got unexpected type for color')
77+
raise TypeError('Got unexpected type of {} for color'.format(type(value)))
7978

8079
@classmethod
8180
def from_dict(cls, the_json):
@@ -90,10 +89,18 @@ def from_dict(cls, the_json):
9089
-------
9190
GeometryProperties
9291
"""
93-
94-
typ = the_json['type']
92+
93+
if not isinstance(the_json, dict):
94+
raise TypeError('This requires a dict. Got type {}'.format(type(the_json)))
95+
96+
typ = the_json.get('type', None) # prevents key error from being thrown if 'type' isn't in the_json
97+
98+
if typ is None:
99+
raise KeyError("the json requires the field 'type'")
100+
95101
if typ != cls._type:
96-
raise ValueError('GeometryProperties cannot be constructed from {}'.format(the_json))
102+
raise ValueError('GeometryProperties cannot be constructed from {}, expecting {}'.format(typ, cls._type))
103+
97104
return cls(
98105
uid=the_json.get('uid', None),
99106
name=the_json.get('name', None),
@@ -168,7 +175,7 @@ def name(self, value):
168175
if value is None or isinstance(value, str):
169176
self._name = value
170177
else:
171-
raise TypeError('Got unexpected type for name')
178+
raise TypeError(f'Got unexpected value of type {type(value)} for name')
172179

173180
@property
174181
def description(self):
@@ -182,7 +189,7 @@ def description(self, value):
182189
if value is None or isinstance(value, str):
183190
self._description = value
184191
else:
185-
raise TypeError('Got unexpected type for description')
192+
raise TypeError(f'Got unexpected value of type {type(value)} for description')
186193

187194
@property
188195
def directory(self):
@@ -198,7 +205,7 @@ def directory(self, value):
198205
return
199206

200207
if not isinstance(value, str):
201-
raise TypeError('Got unexpected type for directory')
208+
raise TypeError(f'Got unexpected value of type {type(value)} for directory')
202209

203210
parts = [entry.strip() for entry in value.split('/')]
204211
self._directory = '/'.join([entry for entry in parts if entry != ''])
@@ -218,7 +225,7 @@ def geometry_properties(self, value):
218225
self._geometry_properties = []
219226
return
220227
if not isinstance(value, list):
221-
raise TypeError('Got unexpected value for geometry properties')
228+
raise TypeError(f'Got unexpected value of type {type(value)} for geometry properties')
222229

223230
self._geometry_properties = []
224231
for entry in value:
@@ -242,8 +249,9 @@ def add_geometry_property(self, entry):
242249
entry = GeometryProperties.from_dict(entry)
243250

244251
if not isinstance(entry, GeometryProperties):
245-
raise TypeError('Got entry of unexpected type for geometry properties list')
246-
self._geometry_properties.append(entry)
252+
raise TypeError(f'Got unexpected value of type {type(entry)} for geometry properties')
253+
254+
self.geometry_properties.append(entry)
247255

248256
def get_geometry_property(self, item):
249257
"""
@@ -303,7 +311,7 @@ def parameters(self, value):
303311
if value is None or isinstance(value, Jsonable):
304312
self._parameters = value
305313
else:
306-
raise TypeError('Got unexpected type for parameters')
314+
raise TypeError(f'Got unexpected value of type {type(value)} for parameters')
307315

308316
@classmethod
309317
def from_dict(cls, the_json):
@@ -319,9 +327,17 @@ def from_dict(cls, the_json):
319327
AnnotationProperties
320328
"""
321329

322-
typ = the_json['type']
330+
if not isinstance(the_json, dict):
331+
raise TypeError('This requires a dict. Got type {}'.format(type(the_json)))
332+
333+
typ = the_json.get('type', None) # prevents key error from being thrown if 'type' isn't in the_json
334+
335+
if typ is None:
336+
raise KeyError("the json requires the field 'type'")
337+
323338
if typ != cls._type:
324-
raise ValueError('AnnotationProperties cannot be constructed from {}'.format(the_json))
339+
raise ValueError('AnnotationProperties cannot be constructed from {}, expecting {}'.format(typ, cls._type))
340+
325341
return cls(
326342
name=the_json.get('name', None),
327343
description=the_json.get('description', None),
@@ -334,6 +350,7 @@ def to_dict(self, parent_dict=None):
334350
Serialize to json.
335351
336352
Parameters
353+
337354
----------
338355
parent_dict : None|Dict
339356
@@ -373,6 +390,7 @@ class AnnotationFeature(Feature):
373390
populated with AnnotationProperties instance.
374391
"""
375392
_allowed_geometries = None
393+
_type = "AnnotationFeature"
376394

377395
@property
378396
def properties(self):
@@ -395,7 +413,7 @@ def properties(self, properties):
395413
elif isinstance(properties, dict):
396414
self._properties = AnnotationProperties.from_dict(properties)
397415
else:
398-
raise TypeError('Got an unexpected type for properties attribute of class {}'.format(self.__class__))
416+
raise TypeError('Got an unexpected type for properties attribute of type {}'.format(properties.__class__))
399417

400418
def get_name(self):
401419
"""
@@ -560,7 +578,7 @@ def _validate_geometry_element(self, geometry):
560578
if geometry is None:
561579
return geometry
562580
if not isinstance(geometry, Geometry):
563-
raise TypeError('geometry must be an instance of Geometry base class')
581+
raise TypeError('geometry must be an instance of Geometry base class. Got {}'.format(type(geometry)))
564582

565583
if self._allowed_geometries is not None and geometry.__class__ not in self._allowed_geometries:
566584
raise TypeError('geometry ({}) is not of one of the allowed types ({})'.format(geometry, self._allowed_geometries))
@@ -577,30 +595,35 @@ def add_geometry_element(self, geometry, properties=None):
577595
"""
578596

579597
if not isinstance(geometry, GeometryObject):
580-
raise TypeError('geometry must be a GeometryObject instance')
598+
raise TypeError('geometry must be a GeometryObject instance. Got {}'.format(type(geometry)))
581599

582600
if properties is None:
583601
properties = GeometryProperties()
602+
584603
if not isinstance(properties, GeometryProperties):
585-
raise TypeError('properties must be a GeometryProperties instance')
604+
raise TypeError('properties must be a GeometryProperties instance. Got {}'.format(type(properties)))
605+
586606
if self.properties is None:
587607
self.properties = AnnotationProperties()
608+
609+
if self.geometry is None:
610+
self.geometry = GeometryCollection()
588611

589612
# handle the geometry
590-
self._geometry = self._validate_geometry_element(
613+
self.geometry = self._validate_geometry_element(
591614
basic_assemble_from_collection(self.geometry, geometry))
592615

593-
# add the geometry property
594-
self.properties.add_geometry_property(properties)
595-
596-
# check that they are in sync
616+
# check that they are in sync and warns before adding the geometry element
597617
if len(self.properties.geometry_properties) != self.geometry_count:
598618
logger.warning(
599619
'There are {} geometry elements defined\n\t'
600620
'and {} geometry properties populated. '
601621
'This is likely to cause problems.'.format(
602622
self.geometry_count, len(self.properties.geometry_properties)))
603623

624+
# add the geometry property
625+
self.properties.add_geometry_property(properties)
626+
604627
def remove_geometry_element(self, item):
605628
"""
606629
Remove the geometry element at the given index
@@ -623,12 +646,33 @@ def remove_geometry_element(self, item):
623646
del self.geometry.collection[index]
624647
del self.properties.geometry_properties[index]
625648

649+
@classmethod
650+
def from_dict(cls, the_json):
651+
if not isinstance(the_json, dict):
652+
raise TypeError('This requires a dict. Got type {}'.format(type(the_json)))
653+
654+
typ = the_json.get('type', None) # prevents key error from being thrown if 'type' isn't in the_json
655+
656+
if typ is None:
657+
raise KeyError("the json requires the field 'type'")
658+
659+
if typ != cls._type:
660+
raise ValueError('AnnotationFeature cannot be constructed from {}, expecting {}'.format(typ, cls._type))
661+
662+
the_id = the_json.get('id', None)
663+
if the_id is None:
664+
the_id = the_json.get('uid', None)
665+
666+
return cls(uid=the_id,
667+
geometry=the_json.get('geometry', None),
668+
properties=the_json.get('properties', None))
626669

627670
class AnnotationCollection(FeatureCollection):
628671
"""
629672
An extension of the FeatureCollection class which has the features are
630673
AnnotationFeature instances.
631674
"""
675+
_type = "AnnotationCollection"
632676

633677
@property
634678
def features(self):
@@ -685,6 +729,27 @@ def __getitem__(self, item):
685729
index = self._feature_dict[item]
686730
return self._features[index]
687731
return self._features[item]
732+
733+
@classmethod
734+
def from_dict(cls, the_json):
735+
if not isinstance(the_json, dict):
736+
raise TypeError('This requires a dict. Got type {}'.format(type(the_json)))
737+
738+
typ = the_json.get('type', None) # prevents key error from being thrown if 'type' isn't in the_json
739+
740+
if typ is None:
741+
raise KeyError("the json requires the field 'type'")
742+
743+
if typ != cls._type:
744+
raise ValueError('AnnotationCollection cannot be constructed from {}, expecting {}'.format(typ, cls._type))
745+
746+
features = the_json.get('features', None)
747+
if features is None:
748+
feature_list = None
749+
else:
750+
feature_list = [AnnotationFeature.from_dict(entry) for entry in features]
751+
752+
return cls(features=feature_list)
688753

689754

690755
class FileAnnotationCollection(Jsonable):
@@ -856,7 +921,8 @@ def from_dict(cls, the_dict):
856921

857922
typ = the_dict.get('type', 'NONE')
858923
if typ != cls._type:
859-
raise ValueError('FileAnnotationCollection cannot be constructed from the input dictionary')
924+
raise ValueError('FileAnnotationCollection cannot be constructed from {}, expecting {}'.format(typ, cls._type))
925+
860926

861927
return cls(
862928
version=the_dict.get('version', 'UNKNOWN'),

0 commit comments

Comments
 (0)