Source code for gpf.tools.metadata

# coding: utf-8
#
# Copyright 2019 Geocom Informatik AG / VertiGIS

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
The metadata module contains functions and classes that help describe data.
"""
import typing as _tp
from warnings import warn as _warn

import gpf.common.const as _const
import gpf.common.textutils as _tu
import gpf.cursors as _cursors
import gpf.tools.fieldutils as _fu
import gpf.tools.queries as _q
from gpf import arcpy as _arcpy


[docs]class DescribeWarning(RuntimeWarning): """ The warning type that is shown when ArcPy's :func:`arcpy.Describe` failed. """ pass
# noinspection PyPep8Naming
[docs]class Describe(object): """ Wrapper class for the ArcPy ``Describe`` object that exposes the most commonly used properties. If ArcPy's :func:`arcpy.Describe` failed, a warning will be shown but no errors will be (re)raised. Any ``Describe`` property that is retrieved, will return ``None`` in this case. If a property does not exist, it will also return ``None``. If this is not desired, consider using the :func:`get` function, which behaves similar to a :func:`dict.get` and can return a user-defined default value if the property was not found. **Params:** - **element** (object): An object, name, or path of an element for which to retrieve its metadata. .. note:: Only a limited amount of properties has been exposed in this class. For a complete list of all possible properties, please have a look `here`_. For these unlisted properties, the same rule applies: if it doesn't exist, ``None`` is returned. If another return value is required, use :func:`get`. .. _here: https://desktop.arcgis.com/en/arcmap/latest/analyze/arcpy-functions/describe-object-properties.htm """ # Exposed properties _ATTR_FIELDS = 'fields' _ATTR_INDEXES = 'indexes' _ATTR_DATATYPE = 'dataType' _ATTR_SHAPETYPE = 'shapeType' _ATTR_DATASETTYPE = 'datasetType' _ATTR_ZAWARE = 'hasZ' _ATTR_MAWARE = 'hasM' _ATTR_EXTENT = 'extent' _ATTR_SPATREF = 'spatialReference' _ATTR_VERSIONED = 'isVersioned' __slots__ = '_obj' def __init__(self, element): self._obj = None try: self._obj = _arcpy.Describe(element) except Exception as e: _warn(str(e), DescribeWarning) def __getattr__(self, name): """ Returns the property value of a Describe object item. """ return self.get(name) def __contains__(self, item): """ Checks if a Describe object has the specified property. """ return hasattr(self._obj, item) def __bool__(self): """ Checks if the Describe object is 'truthy' (i.e. not ``None``). """ if self._obj: return True return False
[docs] def get(self, name, default=None) -> _tp.Any: """ Returns the value of a ``Describe`` object attribute by *name*, returning *default* when it has not been found. This method does not show warnings or raise errors if the attribute does not exist. :param name: The name of the property. :param default: The default value to return in case the property was not found. """ return getattr(self._obj, name, default)
[docs] def num_rows(self, where_clause: _tp.Union[str, _q.Where, None] = None) -> int: """ Returns the number of rows for a table or feature class. If the current ``Describe`` object does not support this action or does not have any rows, 0 will be returned. :param where_clause: An optional where clause to base the row count on. :type where_clause: str, gpf.tools.queries.Where """ field = None if where_clause: if isinstance(where_clause, str): field = _tu.unquote(where_clause.split()[0]) elif hasattr(where_clause, 'fields'): field = where_clause.fields[0] else: raise ValueError('where_clause must be a string or Where instance') try: if field: # Iterate over the dataset rows, using the (first) field from the where_clause with _cursors.SearchCursor(self.catalogPath, field, where_clause=where_clause) as rows: num_rows = sum(1 for _ in rows) del rows else: # Use the ArcPy GetCount() tool for the row count num_rows = int(_arcpy.GetCount_management(self.catalogPath).getOutput(0)) except Exception as e: _warn(str(e), DescribeWarning) num_rows = 0 return num_rows
@property def dataType(self) -> _tp.Union[None, str]: """ Returns the data type for this ``Describe`` object. All ``Describe`` objects should have this property. If it returns ``None``, the object has not been successfully retrieved. """ if not self: return None return self._obj.dataType @property def datasetType(self) -> _tp.Union[None, str]: """ Returns the name of the dataset type (e.g. Table, FeatureClass etc.). If the described object is not a dataset, ``None`` is returned. """ return self.get(Describe._ATTR_DATASETTYPE) @property def shapeType(self) -> _tp.Union[None, str]: """ Returns the geometry type for this ``Describe`` object. This will return 'Polygon', 'Polyline', 'Point', 'Multipoint' or 'MultiPatch' if the described object is a feature class, or ``None`` if it's not. """ return self.get(Describe._ATTR_SHAPETYPE) @property def fields(self) -> _tp.List[_arcpy.Field]: """ Returns a list of all ``Field`` objects (attributes) for this ``Describe`` object. If the described object is not a dataset, this will return an empty list. """ return self.get(Describe._ATTR_FIELDS) or [] @property def indexes(self) -> _tp.List[_arcpy.Index]: """ Returns a list of all ``Index`` objects (attribute indexes) for this ``Describe`` object. If the described object is not a dataset, this will return an empty list. """ return self.get(Describe._ATTR_INDEXES) or []
[docs] def get_fields(self, names_only: bool = True, uppercase: bool = False) -> _tp.List[_tp.Union[str, _arcpy.Field]]: """ Returns a list of all fields in the described object (if any). :param names_only: When ``True`` (default), a list of field *names* instead of ``Field`` instances is returned. :param uppercase: When ``True`` (default=``False``), the returned field names will be uppercase. This also applies when *names_only* is set to return ``Field`` instances. :return: List of field names or ``Field`` instances. """ return _fu.list_fields(self.fields, names_only, uppercase)
[docs] def editable_fields(self, names_only: bool = True, uppercase: bool = False) -> _tp.List[_tp.Union[str, _arcpy.Field]]: """ For data elements that have a *fields* property (e.g. Feature classes, Tables and workspaces), this will return a list of all editable (writable) fields. :param names_only: When ``True`` (default), a list of field *names* instead of ``Field`` instances is returned. :param uppercase: When ``True`` (default=``False``), the returned field names will be uppercase. This also applies when *names_only* is set to return ``Field`` instances. :return: List of field names or ``Field`` instances. """ return [field.name if names_only else field for field in self.get_fields(uppercase=uppercase) if field.editable]
@property def extent(self) -> _arcpy.Extent: """ Returns an ``Extent`` object for this ``Describe`` element. If the described object is not a feature class, this will return an empty ``Extent``. """ return self.get(Describe._ATTR_EXTENT) or _arcpy.Extent() @property def spatialReference(self) -> _arcpy.SpatialReference: """ Returns a ``SpatialReference`` object for this ``Describe`` element. If the described object is not a feature class, this will return an empty ``SpatialReference``. """ return self.get(Describe._ATTR_SPATREF) or _arcpy.SpatialReference() @property def isVersioned(self) -> bool: """ Returns ``True`` if the ``Describe`` element refers to a versioned dataset. If the described object is not a dataset or not versioned, this will return ``False``. """ return self.get(Describe._ATTR_VERSIONED) or False @property def is_pointclass(self) -> bool: """ Returns ``True`` if the described object is a Point feature class. """ return self.get(Describe._ATTR_SHAPETYPE) == _const.SHP_POINT @property def is_multipointclass(self) -> bool: """ Returns ``True`` if the described object is a Multipoint feature class. :rtype: bool """ return self.get(Describe._ATTR_SHAPETYPE) == _const.SHP_MULTIPOINT @property def is_polylineclass(self) -> bool: """ Returns ``True`` if the described object is a Polyline feature class. """ return self.get(Describe._ATTR_SHAPETYPE) == _const.SHP_POLYLINE @property def is_polygonclass(self) -> bool: """ Returns ``True`` if the described object is a Polygon feature class. """ return self.get(Describe._ATTR_SHAPETYPE) == _const.SHP_POLYGON @property def is_multipatchclass(self) -> bool: """ Returns ``True`` if the described object is a MultiPatch feature class. """ return self.get(Describe._ATTR_SHAPETYPE) == _const.SHP_MULTIPATCH @property def is_featureclass(self) -> bool: """ Returns ``True`` if the described object is a feature class. """ return self.get(Describe._ATTR_DATASETTYPE) == _const.DESC_TYPE_FEATURECLASS @property def is_featuredataset(self) -> bool: """ Returns ``True`` if the described object is a feature dataset. """ return self.get(Describe._ATTR_DATASETTYPE) == _const.DESC_TYPE_FEATUREDATASET @property def is_geometricnetwork(self) -> bool: """ Returns ``True`` if the described object is a geometric network. """ return self.get(Describe._ATTR_DATASETTYPE) == _const.DESC_TYPE_GEOMETRICNET @property def is_mosaicdataset(self) -> bool: """ Returns ``True`` if the described object is a mosaic dataset (raster). """ return self.get(Describe._ATTR_DATASETTYPE) == _const.DESC_TYPE_MOSAICRASTER @property def is_rasterdataset(self) -> bool: """ Returns ``True`` if the described object is a raster dataset. """ return self.get(Describe._ATTR_DATASETTYPE) == _const.DESC_TYPE_RASTER @property def is_table(self) -> bool: """ Returns ``True`` if the described object is a table. """ return self.get(Describe._ATTR_DATASETTYPE) == _const.DESC_TYPE_TABLE @property def hasZ(self) -> bool: """ Returns ``True`` if the described object is Z aware (i.e. is 3D). If the object is not a feature class or not Z aware, ``False`` is returned. """ return self.get(Describe._ATTR_ZAWARE) or False @property def hasM(self) -> bool: """ Returns ``True`` if the described object is M aware (i.e. has measures). If the object is not a feature class or not M aware, ``False`` is returned. """ return self.get(Describe._ATTR_MAWARE) or False @property def globalIDFieldName(self) -> _tp.Union[str, None]: """ Global ID field name. Returns ``None`` if the field is missing or if the ``Describe`` object is not a dataset. """ return self.get(_const.DESC_FIELD_GLOBALID) @property def OIDFieldName(self) -> _tp.Union[str, None]: """ Object ID field name. Returns ``None`` if the field is missing or if the ``Describe`` object is not a dataset. """ return self.get(_const.DESC_FIELD_OID) @property def shapeFieldName(self) -> _tp.Union[str, None]: """ Perimeter or polyline length field name. Returns ``None`` if the field is missing or if the ``Describe`` object is not a dataset. """ return self.get(_const.DESC_FIELD_SHAPE) @property def lengthFieldName(self) -> _tp.Union[str, None]: """ Perimeter or polyline length field name. Returns ``None`` if the field is missing or if the ``Describe`` object is not a dataset. """ return self.get(_const.DESC_FIELD_LENGTH) @property def areaFieldName(self) -> _tp.Union[str, None]: """ Polygon area field name. Returns ``None`` if the field is missing or if the ``Describe`` object is not a dataset. """ return self.get(_const.DESC_FIELD_AREA) @property def rasterFieldName(self) -> _tp.Union[str, None]: """ Raster field name. Returns ``None`` if the field is missing or if the ``Describe`` object is not a dataset. """ return self.get(_const.DESC_FIELD_RASTER) @property def subtypeFieldName(self) -> _tp.Union[str, None]: """ Subtype field name. Returns ``None`` if the field is missing or if the ``Describe`` object is not a dataset. """ return self.get(_const.DESC_FIELD_SUBTYPE)