Skip to content

Commit 2202b00

Browse files
Merge branch 'develop' into develop
2 parents 0667072 + f1c005c commit 2202b00

File tree

55 files changed

+642
-344
lines changed

Some content is hidden

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

55 files changed

+642
-344
lines changed

arcpyext/TopicCategory.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# coding=utf-8
2+
"""
3+
This module contains an enumerated type representing ISO Topic Categories.
4+
"""
5+
from aenum import StrEnum
6+
7+
8+
class TopicCategory(StrEnum):
9+
_init_ = "value standard_name display_name portal_name"
10+
11+
BIOTA = "002", "biota", "Biota", "/Categories/Biota"
12+
BOUNDARIES = "003", "boundaries", "Boundaries", "/Categories/Boundaries"
13+
CLIMATOLOGY_METEOROLOGY_ATMOSPHERE = "004", "climatologyMeteorologyAtmosphere", "Climatology, meteorology, and atmosphere", "/Categories/Climatology, meteorology, and atmosphere"
14+
ECONOMY = "005", "economy", "Economy", "/Categories/Economy"
15+
ELEVATION = "006", "elevation", "Elevation", "/Categories/Elevation"
16+
ENVIRONMENT = "007", "environment", "Environment", "/Categories/Environment"
17+
FARMING = "001", "farming", "Farming", "/Categories/Farming"
18+
GEOSCIENTIFIC_INFORMATION = "008", "geoscientificInformation", "Geoscientific information", "/Categories/Geoscientific information"
19+
HEALTH = "009", "health", "Health", "/Categories/Health"
20+
IMAGERY_BASE_MAPS_EARTH_COVER = "010", "imageryBaseMapsEarthCover", "Imagery, basemaps, and Earth cover", "/Catagories/Imagery, basemaps, and Earth cover"
21+
INLAND_WATERS = "012", "inlandWaters", "Inland waters", "/Categories/Inland waters"
22+
INTELLIGENCE_MILITARY = "011", "intelligenceMilitary", "Intelligence and military", "/Categories/Intelligence and military"
23+
LOCATION = "013", "location", "Location", "/Categories/Location"
24+
OCEANS = "014", "oceans", "Oceans", "/Categories/Oceans"
25+
PLANNING_CADASTRE = "015", "planningCadastre", "Planning and cadastre", "/Categories/Planning and cadastre"
26+
SOCIETY = "016", "society", "Society", "/Categories/Society"
27+
STRUCTURE = "017", "structure", "Structure", "/Categories/Structure"
28+
TRANSPORTATION = "018", "transportation", "Transportation", "/Categories/Transportation"
29+
UTILITIES_COMMUNICATION = "019", "utilitiesCommunication", "Utilities and communication", "/Categories/Utilities and communication"

arcpyext/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@
1919
from . import toolbox
2020
from . import schematransform
2121
from . import mapping
22-
from . import publishing
22+
from . import publishing
23+
from .TopicCategory import TopicCategory

arcpyext/_json/json_enum.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@
1212
# pylint: enable=wildcard-import,unused-wildcard-import,wrong-import-order,wrong-import-position
1313

1414
# Standard library imports
15-
from enum import Enum # this is a backport package on Py2
15+
from aenum import Enum # this is a backport package on Py2
1616

17-
class JsonEnum(Enum):
1817

18+
class JsonEnum(Enum):
1919
def __iter__(self):
2020
return iteritems(self._to_jsonable())
2121

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# coding=utf-8
2+
"""
3+
This module contains patches to Esri's *arcpy.metadata.Metadata* class. These patches may insert functionality or fix
4+
issues directly in the *arcpy.metadata.Metadata* class.
5+
"""
6+
import collections.abc
7+
import re
8+
9+
import arcpy
10+
11+
from lxml import etree as ET
12+
13+
from ...TopicCategory import TopicCategory
14+
15+
TOPIC_CATEGORIES_DISPLAY_NAME_TO_ENUM = {e.display_name.lower(): e for e in TopicCategory}
16+
TOPIC_CATEGORIES_STANDARD_NAME_TO_ENUM = {e.standard_name.lower(): e for e in TopicCategory}
17+
TOPIC_CATEGORIES_PORTAL_NAME_TO_ENUM = {e.portal_name.lower(): e for e in TopicCategory}
18+
19+
20+
def _str_to_topic_category(value):
21+
try:
22+
return TopicCategory(value)
23+
except ValueError:
24+
# not a valid input for the enum, try parsing text values
25+
if not isinstance(value, str):
26+
raise
27+
28+
value = value.lower()
29+
for str_dict in [
30+
TOPIC_CATEGORIES_DISPLAY_NAME_TO_ENUM, TOPIC_CATEGORIES_STANDARD_NAME_TO_ENUM,
31+
TOPIC_CATEGORIES_PORTAL_NAME_TO_ENUM
32+
]:
33+
enum_val = str_dict.get(value)
34+
if enum_val:
35+
return enum_val
36+
37+
# didn't find enum value based on string input either, re-raise ValueError
38+
raise
39+
40+
41+
def add_topicCategories():
42+
def topicCategories_getter(self):
43+
metadata_xml = self.xml
44+
45+
if not metadata_xml:
46+
# no XML, not supported, raise error
47+
raise NotImplementedError("Metadata object currently has none or empty XML.")
48+
49+
metadata_xml_tree = ET.ElementTree(ET.fromstring(metadata_xml))
50+
topic_categories_xpath = "/metadata[@xml:lang=\"en\"]/dataIdInfo/tpCat/TopicCatCd"
51+
52+
# get a list of topic elements
53+
topic_category_elements = metadata_xml_tree.xpath(topic_categories_xpath)
54+
55+
if not topic_category_elements:
56+
# no elmeents found, return empty list
57+
return []
58+
59+
# convert each element value to a str
60+
return [TopicCategory(value) for value in [e.attrib["value"] for e in topic_category_elements]]
61+
62+
def topicCategories_setter(self, value):
63+
metadata_xml = self.xml
64+
65+
if not metadata_xml:
66+
# no XML, not supported, raise error
67+
raise NotImplementedError("Metadata object currently has none or empty XML.")
68+
69+
# process value into elements
70+
if isinstance(value, str):
71+
# presume we were supplied a one or more topic categorie as a string, delimited
72+
value = [s.strip() for s in re.split(",|;", value)]
73+
elif isinstance(value, TopicCategory):
74+
# a single enumerated type has been given, put in a list
75+
value = [value]
76+
77+
if value is None:
78+
# value is none, make an empty list
79+
value = []
80+
elif not isinstance(value, collections.abc.Sequence):
81+
raise ValueError("Input 'topicCategories' value is not valid.")
82+
83+
# value is now a sequence presumeably of either an enumerated type already, or a string representing the
84+
# display name, the ISO name, or portal name.
85+
# attempt to resolve each sequence value to an enum and then create element
86+
value = [ET.Element("TopicCatCd", value=_str_to_topic_category(v).value) for v in value]
87+
88+
# set elements in XML
89+
topic_categories_parent_xpath = "/metadata[@xml:lang=\"en\"]/dataIdInfo/tpCat"
90+
metadata_xml_tree = ET.ElementTree(ET.fromstring(metadata_xml))
91+
topic_category_parent_element = next(iter(metadata_xml_tree.xpath(topic_categories_parent_xpath)), None)
92+
topic_category_parent_element.clear()
93+
topic_category_parent_element.extend(value)
94+
95+
# save XML back to arcpy
96+
self.xml = ET.tostring(metadata_xml_tree.getroot(), encoding="utf-8", pretty_print=False)
97+
98+
topicCategories_prop = property(topicCategories_getter, topicCategories_setter)
99+
100+
if hasattr(arcpy.metadata.Metadata, "topicCategories"):
101+
# Esri have added a topicCategories property, bail out
102+
return
103+
104+
# patch arcpy
105+
arcpy.metadata.Metadata.topicCategories = topicCategories_prop
106+
107+
108+
add_topicCategories()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import Metadata

arcpyext/_patches/patches.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ def apply():
2929

3030
if sys.version_info[0] == 3:
3131
from . import _mp
32+
from . import metadata
3233

3334

3435
def fix_mapping_versions():

arcpyext/_str/sql.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
from sqlparse import engine
1414
from sqlparse import filters
1515
from sqlparse import filters, tokens
16-
from sqlparse.compat import text_type
1716
from sqlparse.exceptions import SQLParseError
1817

1918
__all__ = ["format_def_query"]
@@ -53,11 +52,13 @@ def __init__(self, case=None):
5352
self.convert = lambda v: v
5453
else:
5554
case = case.lower()
56-
ignore_quotes_keywords = "_ignore_quotes"
57-
if case.endswith(ignore_quotes_keywords):
58-
self.ignore_quotes = True
59-
case = case[:-len(ignore_quotes_keywords)]
60-
self.convert = getattr(text_type, case)
55+
56+
if case.startswith("upper"):
57+
self.convert = str.upper
58+
elif case.startswith("lower"):
59+
self.convert = str.lower
60+
else:
61+
raise ValueError("Could not determine case conversion option for the following value: {}".format(case))
6162

6263
def process(self, stream):
6364
for ttype, value in stream:
@@ -95,8 +96,7 @@ def format_def_query(def_query, encoding=None, **options):
9596
def validate_options(options):
9697
identifier_case = options.get("identifier_case")
9798
if identifier_case not in [
98-
None, "upper", "upper_ignore_quotes", "lower", "lower_ignore_quotes", "capitalize",
99-
"capitalize_ignore_quotes"
99+
None, "upper", "upper_ignore_quotes", "lower", "lower_ignore_quotes", "capitalize", "capitalize_ignore_quotes"
100100
]:
101101
raise SQLParseError("Invalid value for identifier_case: {0!r}".format(identifier_case))
102102

arcpyext/_version.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
__version__ = "0.7.24"
1+
__version__ = "0.7.27"
22
__author__ = "David Whittingham; David Payne; Adam Kerz; Peter Reyne; Daniel Baternik; Chris Blanchfield; Gary Bagnall"
3-
__copyright__ = "Copyright (C) 2013-2022 David Whittingham"
3+
__copyright__ = "Copyright (C) 2013-2023 David Whittingham"

arcpyext/conversion/ToCsv.py

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,6 @@
1010
install_aliases()
1111
# pylint: enable=wildcard-import,unused-wildcard-import,wrong-import-order,wrong-import-position
1212

13-
# Standard library imports
14-
import csv
15-
import sys
16-
17-
from io import BytesIO
18-
19-
#from io import open
20-
21-
# Third-party imports
22-
import arcpy
23-
2413
# Local imports
2514
from ._ToCsvBase import ToCsvBase
2615

@@ -31,21 +20,21 @@ class ToCsv(ToCsvBase):
3120

3221
def feature_class(self, input_fc, output_fc, use_field_alias_as_column_header=False):
3322

34-
return super().feature_class(input_fc,
35-
output_fc,
36-
use_field_alias_as_column_header=use_field_alias_as_column_header)
23+
return super().feature_class(
24+
input_fc, output_fc, use_field_alias_as_column_header=use_field_alias_as_column_header
25+
)
3726

3827
def table(self, input_table, output_table, use_field_alias_as_column_header=False):
39-
return super().table(input_table,
40-
output_table,
41-
use_field_alias_as_column_header=use_field_alias_as_column_header)
28+
return super().table(
29+
input_table, output_table, use_field_alias_as_column_header=use_field_alias_as_column_header
30+
)
4231

4332
def relationship_class(self, input_rel, output_rel):
4433
return super().relationship_class(input_rel, output_rel)
4534

4635
def workspace(self, input_workspace, output_path, use_field_alias_as_column_header=False):
47-
return super().workspace(input_workspace,
48-
output_path,
49-
use_field_alias_as_column_header=use_field_alias_as_column_header)
36+
return super().workspace(
37+
input_workspace, output_path, use_field_alias_as_column_header=use_field_alias_as_column_header
38+
)
5039

5140
#endregion

arcpyext/conversion/ToOfficeOpenXmlWorkbook.py

Lines changed: 45 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -40,19 +40,22 @@ def feature_class(self, input_fc, output_workbook, use_field_alias_as_column_hea
4040
raise ValueError("output_workbook already exists.")
4141

4242
# get input feature class description for copy process
43-
d = arcpy.Describe(input_fc)
43+
input_fc_desc = arcpy.Describe(input_fc)
4444

45-
if not d.dataType == "FeatureClass":
45+
if not input_fc_desc.dataType == "FeatureClass":
4646
raise ValueError("input_fc is not of type 'FeatureClass'.")
4747

4848
output_workbook = xlsxwriter.Workbook(str(output_workbook))
4949

50-
sheet_name = self._feature_class_default_name(d, output_workbook)
50+
sheet_name = self._feature_class_default_name(input_fc_desc, output_workbook)
5151

52-
self._feature_class(d, output_workbook, sheet_name, use_field_alias_as_column_header)
52+
self._feature_class(input_fc_desc, output_workbook, sheet_name, use_field_alias_as_column_header)
5353

5454
output_workbook.close()
5555

56+
del input_fc_desc
57+
arcpy.management.ClearWorkspaceCache()
58+
5659
def table(self, input_table, output_workbook, use_field_alias_as_column_header=False):
5760
if not arcpy.Exists(input_table):
5861
raise ValueError("input_table does not exist.")
@@ -63,55 +66,68 @@ def table(self, input_table, output_workbook, use_field_alias_as_column_header=F
6366
raise ValueError("output_table already exists.")
6467

6568
# get input feature class description for copy process
66-
d = arcpy.Describe(input_table)
69+
input_table_desc = arcpy.Describe(input_table)
6770

68-
if not d.dataType == "Table":
71+
if not input_table_desc.dataType == "Table":
6972
raise ValueError("input_table is not of type 'Table'.")
7073

7174
output_workbook = xlsxwriter.Workbook(str(output_workbook))
7275

73-
sheet_name = self._table_default_name(d, output_workbook)
76+
sheet_name = self._table_default_name(input_table_desc, output_workbook)
7477

75-
self._table(d, output_workbook, sheet_name, use_field_alias_as_column_header)
78+
self._table(input_table_desc, output_workbook, sheet_name, use_field_alias_as_column_header)
7679

7780
output_workbook.close()
7881

82+
del input_table_desc
83+
arcpy.management.ClearWorkspaceCache()
84+
7985
def relationship_class(self, input_rel, output_rel):
8086
return super().relationship_class(input_rel, output_rel)
8187

8288
def workspace(self, input_workspace, output_path, use_field_alias_as_column_header=False):
8389
if not arcpy.Exists(input_workspace):
8490
raise ValueError("input_workspace does not exist.")
8591

86-
d = arcpy.Describe(input_workspace)
92+
input_workspace_desc = arcpy.Describe(input_workspace)
8793

88-
if not d.dataType == "Workspace":
94+
if not input_workspace_desc.dataType == "Workspace":
8995
raise ValueError("input_workspace is not of type 'Workspace'.")
9096

9197
# get output_path as a Path object
9298
output_path = Path(output_path).resolve()
9399

94100
output_workbook = self._create_output_workspace(
95-
output_path, use_field_alias_as_column_header=use_field_alias_as_column_header)
101+
output_path, use_field_alias_as_column_header=use_field_alias_as_column_header
102+
)
96103

97-
for c in d.children:
104+
for c in input_workspace_desc.children:
98105
if c.dataType == 'FeatureClass':
99-
self._feature_class(c,
100-
output_workbook,
101-
self._feature_class_default_name(c, output_workbook),
102-
use_field_alias_as_column_header=use_field_alias_as_column_header)
106+
self._feature_class(
107+
c,
108+
output_workbook,
109+
self._feature_class_default_name(c, output_workbook),
110+
use_field_alias_as_column_header=use_field_alias_as_column_header
111+
)
103112
elif c.dataType == 'Table':
104-
self._table(c,
105-
output_workbook,
106-
self._table_default_name(c, output_workbook),
107-
use_field_alias_as_column_header=use_field_alias_as_column_header)
113+
self._table(
114+
c,
115+
output_workbook,
116+
self._table_default_name(c, output_workbook),
117+
use_field_alias_as_column_header=use_field_alias_as_column_header
118+
)
108119
elif c.dataType == 'RelationshipClass':
109-
self._relationship_class(c,
110-
self._relationship_class_default_name(c, output_path),
111-
use_field_alias_as_column_header=use_field_alias_as_column_header)
120+
self._relationship_class(
121+
c,
122+
self._relationship_class_default_name(c, output_path),
123+
use_field_alias_as_column_header=use_field_alias_as_column_header
124+
)
112125

113126
output_workbook.close()
114127

128+
del input_workspace_desc
129+
arcpy.management.ClearWorkspaceCache()
130+
115131
#endregion
116132

117133
#region Private overrides
@@ -167,10 +183,12 @@ def _dataset_to_ooxml(self, dataset, workbook, sheet_name, use_field_alias_as_co
167183
# create table layout, if any data was written
168184
if row_no > 0:
169185
header_attr = "name" if use_field_alias_as_column_header == False else "aliasName"
170-
worksheet.add_table(0, 0, row_no - 1,
171-
len(fields) - 1, {"columns": [{
172-
"header": getattr(f, header_attr)
173-
} for f in fields]})
186+
worksheet.add_table(
187+
0, 0, row_no - 1,
188+
len(fields) - 1, {"columns": [{
189+
"header": getattr(f, header_attr)
190+
} for f in fields]}
191+
)
174192

175193
def _get_default_name(self, desc, output_workbook, **kwargs):
176194
# get the default name for OOXML, respecting the 31 character limit and avoiding collisions

0 commit comments

Comments
 (0)