arc-utils 1.2.1__tar.gz → 1.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. arc_utils-1.3/PKG-INFO +117 -0
  2. arc_utils-1.3/README.md +98 -0
  3. arc_utils-1.3/arc_utils/_inputs.py +105 -0
  4. {arc_utils-1.2.1 → arc_utils-1.3}/arc_utils/aprx.py +12 -4
  5. {arc_utils-1.2.1 → arc_utils-1.3}/arc_utils/gdb.py +32 -13
  6. arc_utils-1.3/arc_utils/output.py +111 -0
  7. {arc_utils-1.2.1 → arc_utils-1.3}/arc_utils/table.py +138 -131
  8. arc_utils-1.3/arc_utils.egg-info/PKG-INFO +117 -0
  9. {arc_utils-1.2.1 → arc_utils-1.3}/arc_utils.egg-info/SOURCES.txt +1 -0
  10. {arc_utils-1.2.1 → arc_utils-1.3}/pyproject.toml +2 -2
  11. {arc_utils-1.2.1 → arc_utils-1.3}/setup.cfg +1 -1
  12. {arc_utils-1.2.1 → arc_utils-1.3}/tests/conftest.py +1 -1
  13. {arc_utils-1.2.1 → arc_utils-1.3}/tests/test_aprx.py +12 -0
  14. arc_utils-1.3/tests/test_gdb.py +27 -0
  15. arc_utils-1.3/tests/test_table.py +129 -0
  16. arc_utils-1.2.1/PKG-INFO +0 -62
  17. arc_utils-1.2.1/README.md +0 -43
  18. arc_utils-1.2.1/arc_utils/output.py +0 -80
  19. arc_utils-1.2.1/arc_utils.egg-info/PKG-INFO +0 -62
  20. arc_utils-1.2.1/tests/test_gdb.py +0 -14
  21. arc_utils-1.2.1/tests/test_table.py +0 -69
  22. {arc_utils-1.2.1 → arc_utils-1.3}/LICENSE.txt +0 -0
  23. {arc_utils-1.2.1 → arc_utils-1.3}/arc_utils/__init__.py +0 -0
  24. {arc_utils-1.2.1 → arc_utils-1.3}/arc_utils.egg-info/dependency_links.txt +0 -0
  25. {arc_utils-1.2.1 → arc_utils-1.3}/arc_utils.egg-info/top_level.txt +0 -0
  26. {arc_utils-1.2.1 → arc_utils-1.3}/examples/extract-featureclass-details.py +0 -0
  27. {arc_utils-1.2.1 → arc_utils-1.3}/setup.py +0 -0
  28. {arc_utils-1.2.1 → arc_utils-1.3}/tests/__init__.py +0 -0
arc_utils-1.3/PKG-INFO ADDED
@@ -0,0 +1,117 @@
1
+ Metadata-Version: 2.4
2
+ Name: arc-utils
3
+ Version: 1.3
4
+ Summary: Python utilities for use with Esri ArcGIS Pro software
5
+ Author-email: Grant Herbert <gdherbert@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/gdherbert/arc_utils
8
+ Project-URL: Download, https://github.com/gdherbert/arc_utils/tree/1.3
9
+ Keywords: esri,arcpy,arcgis
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Operating System :: Microsoft :: Windows
15
+ Requires-Python: >=3.11
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE.txt
18
+ Dynamic: license-file
19
+
20
+ # README #
21
+
22
+
23
+ ### What is this repository for? ###
24
+ * A collection of (mostly) arcpy related utilities for use within ArcGIS Pro (Python 3).
25
+ Shortcuts for accessing elements of a map document, performing various data discovery tasks on a table or geodatabase
26
+
27
+ ### How do I get set up? ###
28
+
29
+ Install from PyPI:
30
+
31
+ ```bash
32
+ pip install arc-utils
33
+ ```
34
+
35
+ For local development and editable usage from clone:
36
+
37
+ ```bash
38
+ python -m pip install -e .
39
+ ```
40
+
41
+ To use the utilities:
42
+
43
+ * in the python window in ArcGIS Pro
44
+
45
+ ``import arc_utils as au``
46
+
47
+ * typing ``au.`` will now reveal the modules and functions available to you if you have autocomplete on
48
+
49
+ * Depends on arcpy (only available with Esri license)
50
+
51
+ ### Example usage ###
52
+
53
+ ``import arc_utils as au``
54
+
55
+ ``tbl = au.table.TableObj(path to featureclass)``
56
+
57
+ # list all fields
58
+
59
+ ``print(tbl.fields)``
60
+
61
+ ``pro_doc = au.aprx.AprxObj(“CURRENT”)``
62
+
63
+ ``pro_doc.maps`` >> [<arcpy._mp.Map object at 0x000001A4AECBF108>, <arcpy._mp.Map object at 0x000001A4AEBB6608>]
64
+
65
+ ``pro_doc.maps[0].name`` >> ‘Map’
66
+
67
+ ### Accepted Input Types ###
68
+
69
+ Most path parameters now accept:
70
+
71
+ * string path
72
+ * ``pathlib.Path`` / other path-like objects
73
+ * wrapper objects with ``.path``
74
+ * ArcGIS layer/table objects with ``.catalogPath`` or ``.dataSource`` (where applicable)
75
+
76
+ Invalid paths raise ``ValueError("invalid path")``.
77
+
78
+ Constructor acceptance is preserved (for example, passing path-like and wrapper objects directly to ``TableObj``, ``GDBObj``, and ``AprxObj``).
79
+
80
+ ### Exporting field values to Excel ###
81
+
82
+ ```python
83
+ import arc_utils as au
84
+ from openpyxl import Workbook
85
+
86
+ # Single table export with class method
87
+ tbl = au.table.TableObj(r"C:\path\to\featureclass")
88
+ wb = Workbook()
89
+ ws = wb.active
90
+ tbl.export_fields_to_worksheet(ws)
91
+ wb.save(r"C:\path\to\schema_values.xlsx")
92
+ ```
93
+
94
+ ```python
95
+ # Multi-layer export with module function
96
+ layers = [r"C:\path\to\layer1", r"C:\path\to\layer2"]
97
+ au.table.export_field_sets(layers, r"C:\path\to\all_layers.xlsx")
98
+ ```
99
+
100
+ ```python
101
+ # Worksheet naming behavior:
102
+ # use_lyr_alias=True -> prefer alias/name (human-readable)
103
+ # use_lyr_alias=False -> use full table path (traceable)
104
+ au.table.export_field_sets(layers, r"C:\path\to\all_layers.xlsx", use_lyr_alias=True)
105
+ au.table.export_field_sets(layers, r"C:\path\to\all_layers_by_path.xlsx", use_lyr_alias=False)
106
+ ```
107
+
108
+ ```python
109
+ # Duplicate detection outputs
110
+ tbl = au.table.TableObj(r"C:\path\to\featureclass")
111
+ dup_values = tbl.find_duplicate_field_values("STATUS", output="set")
112
+ dup_rows_df = tbl.find_duplicate_field_values(["STATUS", "TYPE"], output="df")
113
+ ```
114
+
115
+ ### Contribution guidelines ###
116
+
117
+ Contributions welcomed, this is a starting point for various utilities that I think could be useful within ArcGIS Pro.
@@ -0,0 +1,98 @@
1
+ # README #
2
+
3
+
4
+ ### What is this repository for? ###
5
+ * A collection of (mostly) arcpy related utilities for use within ArcGIS Pro (Python 3).
6
+ Shortcuts for accessing elements of a map document, performing various data discovery tasks on a table or geodatabase
7
+
8
+ ### How do I get set up? ###
9
+
10
+ Install from PyPI:
11
+
12
+ ```bash
13
+ pip install arc-utils
14
+ ```
15
+
16
+ For local development and editable usage from clone:
17
+
18
+ ```bash
19
+ python -m pip install -e .
20
+ ```
21
+
22
+ To use the utilities:
23
+
24
+ * in the python window in ArcGIS Pro
25
+
26
+ ``import arc_utils as au``
27
+
28
+ * typing ``au.`` will now reveal the modules and functions available to you if you have autocomplete on
29
+
30
+ * Depends on arcpy (only available with Esri license)
31
+
32
+ ### Example usage ###
33
+
34
+ ``import arc_utils as au``
35
+
36
+ ``tbl = au.table.TableObj(path to featureclass)``
37
+
38
+ # list all fields
39
+
40
+ ``print(tbl.fields)``
41
+
42
+ ``pro_doc = au.aprx.AprxObj(“CURRENT”)``
43
+
44
+ ``pro_doc.maps`` >> [<arcpy._mp.Map object at 0x000001A4AECBF108>, <arcpy._mp.Map object at 0x000001A4AEBB6608>]
45
+
46
+ ``pro_doc.maps[0].name`` >> ‘Map’
47
+
48
+ ### Accepted Input Types ###
49
+
50
+ Most path parameters now accept:
51
+
52
+ * string path
53
+ * ``pathlib.Path`` / other path-like objects
54
+ * wrapper objects with ``.path``
55
+ * ArcGIS layer/table objects with ``.catalogPath`` or ``.dataSource`` (where applicable)
56
+
57
+ Invalid paths raise ``ValueError("invalid path")``.
58
+
59
+ Constructor acceptance is preserved (for example, passing path-like and wrapper objects directly to ``TableObj``, ``GDBObj``, and ``AprxObj``).
60
+
61
+ ### Exporting field values to Excel ###
62
+
63
+ ```python
64
+ import arc_utils as au
65
+ from openpyxl import Workbook
66
+
67
+ # Single table export with class method
68
+ tbl = au.table.TableObj(r"C:\path\to\featureclass")
69
+ wb = Workbook()
70
+ ws = wb.active
71
+ tbl.export_fields_to_worksheet(ws)
72
+ wb.save(r"C:\path\to\schema_values.xlsx")
73
+ ```
74
+
75
+ ```python
76
+ # Multi-layer export with module function
77
+ layers = [r"C:\path\to\layer1", r"C:\path\to\layer2"]
78
+ au.table.export_field_sets(layers, r"C:\path\to\all_layers.xlsx")
79
+ ```
80
+
81
+ ```python
82
+ # Worksheet naming behavior:
83
+ # use_lyr_alias=True -> prefer alias/name (human-readable)
84
+ # use_lyr_alias=False -> use full table path (traceable)
85
+ au.table.export_field_sets(layers, r"C:\path\to\all_layers.xlsx", use_lyr_alias=True)
86
+ au.table.export_field_sets(layers, r"C:\path\to\all_layers_by_path.xlsx", use_lyr_alias=False)
87
+ ```
88
+
89
+ ```python
90
+ # Duplicate detection outputs
91
+ tbl = au.table.TableObj(r"C:\path\to\featureclass")
92
+ dup_values = tbl.find_duplicate_field_values("STATUS", output="set")
93
+ dup_rows_df = tbl.find_duplicate_field_values(["STATUS", "TYPE"], output="df")
94
+ ```
95
+
96
+ ### Contribution guidelines ###
97
+
98
+ Contributions welcomed, this is a starting point for various utilities that I think could be useful within ArcGIS Pro.
@@ -0,0 +1,105 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Shared input coercion helpers for arc_utils."""
3
+ import os
4
+ import arcpy
5
+
6
+
7
+ def _normalize_to_sequence(values):
8
+ """Return a list from single or multi-value inputs.
9
+
10
+ Strings and path-like values are treated as a single item.
11
+ """
12
+ if isinstance(values, (list, tuple, set)):
13
+ return list(values)
14
+ return [values]
15
+
16
+
17
+ def _to_path_str(value):
18
+ """Convert path-like values to str where possible."""
19
+ if value is None:
20
+ return None
21
+ try:
22
+ value = os.fspath(value)
23
+ except TypeError:
24
+ return None
25
+ if isinstance(value, bytes):
26
+ value = value.decode()
27
+ return value
28
+
29
+
30
+ def _resolve_dataset_path(value, arg_name="value"):
31
+ """Resolve supported dataset inputs to a path string.
32
+
33
+ Supported inputs:
34
+ - str and pathlib.Path / os.PathLike
35
+ - ArcGIS Pro layer-name strings (resolved via arcpy.Describe(...).catalogPath)
36
+ - objects exposing .path (wrapper objects)
37
+ - ArcGIS Pro map/layer objects exposing .catalogPath or .dataSource
38
+ """
39
+ def _resolve_candidate(candidate):
40
+ """Resolve map layer names and object references to catalog paths."""
41
+ try:
42
+ desc = arcpy.Describe(candidate)
43
+ if hasattr(desc, "catalogPath") and desc.catalogPath:
44
+ return os.path.abspath(desc.catalogPath)
45
+ except Exception:
46
+ pass
47
+ return os.path.abspath(candidate)
48
+
49
+ for attr in ("path", "catalogPath", "dataSource"):
50
+ if hasattr(value, attr):
51
+ candidate = _to_path_str(getattr(value, attr))
52
+ if candidate:
53
+ return _resolve_candidate(candidate)
54
+
55
+ direct = _to_path_str(value)
56
+ if direct:
57
+ return _resolve_candidate(direct)
58
+
59
+ raise TypeError("{} must be a path-like string or object with a valid path".format(arg_name))
60
+
61
+
62
+ def _resolve_aprx_path(value, arg_name="value"):
63
+ """Resolve Aprx path input, preserving CURRENT special value."""
64
+ direct = _to_path_str(value)
65
+ if isinstance(direct, str) and direct.upper() == "CURRENT":
66
+ return "CURRENT"
67
+
68
+ for attr in ("path", "catalogPath", "dataSource"):
69
+ if hasattr(value, attr):
70
+ candidate = _to_path_str(getattr(value, attr))
71
+ if isinstance(candidate, str) and candidate.upper() == "CURRENT":
72
+ return "CURRENT"
73
+ if candidate:
74
+ return os.path.abspath(candidate)
75
+
76
+ if direct:
77
+ return os.path.abspath(direct)
78
+
79
+ raise TypeError("{} must be a path-like string or object with a valid path".format(arg_name))
80
+
81
+
82
+ def _ensure_valid_path(path):
83
+ """Validate ArcGIS path-like input and raise a consistent error."""
84
+ if not arcpy.Exists(path):
85
+ raise ValueError("invalid path")
86
+ return path
87
+
88
+
89
+ def _input_display_name(value, default_name=None):
90
+ """Best-effort human-readable name for logs and worksheet names."""
91
+ for attr in ("name",):
92
+ if hasattr(value, attr):
93
+ candidate = getattr(value, attr)
94
+ if candidate:
95
+ return str(candidate)
96
+
97
+ for attr in ("path", "catalogPath", "dataSource"):
98
+ if hasattr(value, attr):
99
+ candidate = getattr(value, attr)
100
+ if candidate:
101
+ return str(candidate)
102
+
103
+ if default_name is not None:
104
+ return str(default_name)
105
+ return str(value)
@@ -3,20 +3,28 @@
3
3
  """
4
4
 
5
5
  import arcpy
6
+ import os
7
+ from ._inputs import _resolve_aprx_path
6
8
 
7
9
  class AprxObj(object):
8
10
  """ provide methods for working with a ArcGIS Pro aprx file
9
11
  all standard arcpy methods are available via .aprx
10
12
  Usage: pro_doc = arc_utils.aprx.AprxObj(path)
11
13
  :param
12
- path: a string representing an aprx file,
13
- or "CURRENT" if used in ArcGIS Pro Python window
14
+ path: aprx input accepted as
15
+ - string path
16
+ - pathlib.Path / os.PathLike
17
+ - wrapper object exposing .path
18
+ - "CURRENT" if used in ArcGIS Pro Python window
19
+ invalid paths raise ValueError("invalid path")
14
20
  """
15
21
  def __init__(self, aprx_path):
16
22
  """ :param path to aprx or "CURRENT"
17
23
  """
18
- self.aprx = arcpy.mp.ArcGISProject(aprx_path)
19
- self.path = aprx_path
24
+ self.path = _resolve_aprx_path(aprx_path, arg_name="aprx_path")
25
+ if self.path != "CURRENT" and not os.path.exists(self.path):
26
+ raise ValueError("invalid path")
27
+ self.aprx = arcpy.mp.ArcGISProject(self.path)
20
28
  self.maps = self.get_maps()
21
29
  self.layouts = self.get_layouts()
22
30
 
@@ -1,23 +1,29 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """utilities for working with geodatabases and reporting on geodatabase contents
3
3
  """
4
- from __future__ import print_function, unicode_literals, absolute_import
5
4
  import os
6
5
  import arcpy
7
6
  from .output import get_valid_output_path
8
7
  from .output import output_msg
8
+ from ._inputs import _ensure_valid_path
9
+ from ._inputs import _normalize_to_sequence
10
+ from ._inputs import _resolve_dataset_path
9
11
 
10
12
  class GDBObj(object):
11
13
  """ provide properties for working with a GDB
12
14
  Usage: gdb = arc_utils.gdb.GDBObj(path)
13
15
  :param
14
- path: a string representing a GDB connection/path
16
+ path: geodatabase input accepted as
17
+ - string path
18
+ - pathlib.Path / os.PathLike
19
+ - wrapper object exposing .path
20
+ - ArcGIS object exposing .catalogPath or .dataSource
15
21
  """
16
22
  def __init__(self, gdb_path):
17
23
  """ sets up reference to table
18
24
  adds properties and methods
19
25
  """
20
- self.path = gdb_path
26
+ self.path = _ensure_valid_path(_resolve_dataset_path(gdb_path, arg_name="gdb_path"))
21
27
  self.describe_obj = self._describe_object()
22
28
  self.feature_classes = self.get_feature_class_names()
23
29
  self.tables = self.get_table_names()
@@ -77,8 +83,8 @@ def report_all_fc_as_text(geodatabase, output_file=None, sep='\t'):
77
83
  """Create a text report of all fields in all featureclasses/tables from a geodatabase
78
84
  to specified output file.
79
85
 
80
- :param geodatabase {String}
81
- Path or reference to a geodatabase.
86
+ :param geodatabase {String|pathlike|object}
87
+ Geodatabase path or object reference.
82
88
 
83
89
  :param output_file {String}
84
90
  Path or reference to a text file. If not supplied defaults to gdb directory.
@@ -88,10 +94,13 @@ def report_all_fc_as_text(geodatabase, output_file=None, sep='\t'):
88
94
  """
89
95
  default_env = arcpy.env.workspace
90
96
  try:
97
+ geodatabase = _ensure_valid_path(_resolve_dataset_path(geodatabase, arg_name="geodatabase"))
91
98
  desc = arcpy.Describe(geodatabase)
92
99
  arcpy.env.workspace = geodatabase
93
100
  if not output_file:
94
101
  path = get_valid_output_path(desc.Path)
102
+ if not path:
103
+ raise ValueError("invalid output path")
95
104
  output_file = os.path.join(path, desc.name.split(".")[0] + ".txt")
96
105
 
97
106
  output_msg("Writing to: {0}".format(output_file))
@@ -124,7 +133,7 @@ def report_all_fc_as_text(geodatabase, output_file=None, sep='\t'):
124
133
 
125
134
  for dataset in datasets:
126
135
  for fc in arcpy.ListFeatureClasses(feature_dataset=dataset):
127
- output_msg("Processing Dataset: {0} \ FeatureClass: {1}".format(dataset, fc))
136
+ output_msg("Processing Dataset: {0} \\ FeatureClass: {1}".format(dataset, fc))
128
137
  try:
129
138
  fields = arcpy.ListFields(fc)
130
139
  str_output = ""
@@ -151,15 +160,18 @@ def export_all_domains(geodatabase, workspace=None):
151
160
  """Output all the domains in a geodatabase
152
161
  to tables in a workspace.
153
162
 
154
- :param geodatabase {String}:
163
+ :param geodatabase {String|pathlike|object}:
155
164
  Path or reference to input geodatabase.
156
165
 
157
- :param workspace {String}: Optional
166
+ :param workspace {String|pathlike|object}: Optional
158
167
  Path to output folder or geodatabase. Defaults to input geodatabase.
159
168
  """
160
169
  try:
170
+ geodatabase = _ensure_valid_path(_resolve_dataset_path(geodatabase, arg_name="geodatabase"))
161
171
  if not workspace:
162
172
  workspace = geodatabase
173
+ else:
174
+ workspace = _resolve_dataset_path(workspace, arg_name="workspace")
163
175
  domains = arcpy.da.ListDomains(geodatabase)
164
176
  for domain in domains:
165
177
  dname = arcpy.ValidateTableName(domain.name + '_domain', workspace)
@@ -179,13 +191,20 @@ def export_all_domains(geodatabase, workspace=None):
179
191
  def import_tables_as_domains(tables, geodatabase):
180
192
  """import tables as domains into geodatabase. Expects fields 'codedValues' and 'description'
181
193
 
182
- :param tables {string}
183
- path or array of paths of input tables containing domain data
194
+ :param tables {string|pathlike|object|array}
195
+ table path/object or array of table paths/objects containing domain data
184
196
 
185
- :param geodatabase
186
- Path or reference to geodatabase."""
197
+ :param geodatabase {String|pathlike|object}
198
+ Path or reference to geodatabase.
199
+
200
+ Notes:
201
+ Single string inputs are treated as a single table path (not iterated per character).
202
+ Invalid paths raise ValueError("invalid path").
203
+ """
187
204
  try:
188
- for table in tables:
205
+ geodatabase = _ensure_valid_path(_resolve_dataset_path(geodatabase, arg_name="geodatabase"))
206
+ for table in _normalize_to_sequence(tables):
207
+ table = _ensure_valid_path(_resolve_dataset_path(table, arg_name="tables"))
189
208
  desc = arcpy.Describe(table)
190
209
  dname = desc.name.replace("_domain", "")
191
210
  arcpy.TableToDomain_management(table, "codedValues", "description", geodatabase, dname)
@@ -0,0 +1,111 @@
1
+ # -*- coding: utf-8 -*-
2
+ """output related utilities
3
+ """
4
+ import arcpy
5
+ import os
6
+
7
+ def output_msg(msg, severity=0):
8
+ """Output msg to print and/or to Arc. Useful to include in a tool
9
+ that can be run in Python or in ArcGIS
10
+
11
+ msg{String}:
12
+ message to output.
13
+
14
+ severity(integer):
15
+ severity = 0 (none), 1 (warning), 2 (error)
16
+ usage:
17
+ output_msg("message")
18
+
19
+ function from http://help.arcgis.com/en/arcgisdesktop/10.0/help/index.html#//00150000000p000000.htm
20
+ """
21
+
22
+ # Adds a Message (in case this is run as a tool)
23
+ # and also prints the message to the screen (standard output)
24
+ #
25
+ print(msg)
26
+
27
+ # Split the message on \n first, so that if it's multiple lines,
28
+ # a GPMessage will be added for each line
29
+ try:
30
+ for string in msg.split('\n'):
31
+ # Add appropriate geoprocessing message
32
+ #
33
+ if severity == 0:
34
+ arcpy.AddMessage(string)
35
+ elif severity == 1:
36
+ arcpy.AddWarning(string)
37
+ elif severity == 2:
38
+ arcpy.AddError(string)
39
+ except:
40
+ pass
41
+
42
+
43
+ def get_valid_output_path(path, return_folder_only=True, make_dir=True, fallback_to_user_folder=True):
44
+ """ return a valid path or empty string if not valid.
45
+ If return_folder_only is True (default) and a gdb is passed as path,
46
+ will return the folder containing gdb.
47
+ If make_dir is True (default), path will be created if it doesn't exist..
48
+ If fallback_to_user_folder is True (default), will fallback to userprofile\documents folder
49
+ path {String}:
50
+ filepath
51
+ return_folder_only {Boolean}:
52
+ returned path must be a folder, not a .gdb
53
+ make_dir {Boolean}:
54
+ make the directory if not exists
55
+ fallback_to_user_folder {Boolean}:
56
+ if path not valid, fallback to userprofile\documents folder (if available)
57
+ :return path or empty string
58
+ """
59
+ def _fallback_user_folder():
60
+ if not fallback_to_user_folder:
61
+ return ''
62
+ user_profile = os.environ.get('USERPROFILE') or os.path.expanduser('~')
63
+ if not user_profile:
64
+ return ''
65
+ docs_dir = os.path.join(user_profile, 'Documents')
66
+ if os.path.isdir(docs_dir):
67
+ return docs_dir
68
+ if os.path.isdir(user_profile):
69
+ return user_profile
70
+ return ''
71
+
72
+ try:
73
+ path = os.fspath(path)
74
+ except (TypeError, ValueError):
75
+ return _fallback_user_folder()
76
+
77
+ if not path:
78
+ return _fallback_user_folder()
79
+
80
+ path = os.path.abspath(os.path.expanduser(path))
81
+ report_dir = path
82
+
83
+ if os.path.isdir(path):
84
+ if return_folder_only and path.lower().endswith('.gdb'):
85
+ try:
86
+ report_dir = arcpy.Describe(path).Path
87
+ except Exception:
88
+ return _fallback_user_folder()
89
+ elif make_dir:
90
+ try:
91
+ os.makedirs(path)
92
+ report_dir = path
93
+ except OSError:
94
+ return _fallback_user_folder()
95
+ else:
96
+ return _fallback_user_folder()
97
+
98
+ if not os.path.isdir(report_dir):
99
+ return _fallback_user_folder()
100
+
101
+ return report_dir
102
+
103
+
104
+ def output_to_file(data, path, filename):
105
+ """send result string to file"""
106
+ path = get_valid_output_path(path)
107
+ if not path:
108
+ raise ValueError("invalid output path")
109
+ output_file = os.path.join(path, filename)
110
+ with open(output_file, "w") as output:
111
+ output.write('{}'.format(data))