コード例 #1
0
ファイル: paths.py プロジェクト: geocom-gis/gpf3
def split_gdbpath(path: str, remove_qualifier: bool = True) -> tuple:
    """
    Splits the Esri Geodatabase *path* into a ``tuple`` of *(workspace, feature_dataset, feature_class/table)*.
    If any of the output tuple parts is not present, these will be set to an empty string.
    Note that if the path refers to a table or feature class that is not stored in a feature dataset,
    the *feature_dataset* part in the output tuple will be an empty string and the last part will contain the table
    or feature class name.

    Examples:

        >>> split_gdbpath('C:/test.gdb/table')
        ('C:\\test.gdb', '', 'table')
        >>> split_gdbpath('C:/test.sde/qualifier.featureclass', False)
        ('C:\\test.sde', '', 'qualifier.featureclass')
        >>> split_gdbpath('C:/test.sde/qualifier.fds/qualifier.fc')
        ('C:\\test.sde', 'fds', 'fc')

    :param path:                The full path to the Geodatabase feature class or table.
    :param remove_qualifier:    If ``True``, the split parts are "unqualified". If any DB qualifiers exist in the path,
                                they will be removed. This is the default behaviour.
                                Set this parameter to ``False`` if qualifiers must be persisted.
    :raises ValueError:         If the given path does not seem to be a Geodatabase path or
                                there are more than 2 levels in the Geodatabase.
    """
    _vld.pass_if(is_gdbpath(path), ValueError,
                 f'{path} does not seem to be a valid Esri Geodatabase path')
    path = normalize(path, False)
    path_parts = path.split('\\')
    num_parts = len(path_parts)

    # Find the part that contains the GDB extension
    endpos = 1
    for endpos, p in enumerate(path_parts):
        if p.lower().endswith(ESRI_GDB_EXTENSIONS):
            break
    startpos = endpos + 1

    # Set the workspace path and return it if the path isn't any longer than that
    ws_path = '\\'.join(path_parts[:startpos])
    if num_parts == startpos:
        return ws_path, _const.CHAR_EMPTY, _const.CHAR_EMPTY

    # If there are more than 2 levels (i.e. feature dataset and feature class), raise an error
    _vld.raise_if(num_parts > startpos + 2, ValueError,
                  'Geodatabase path cannot be more than 2 levels deep')

    last_parts = [(unqualify(p) if remove_qualifier else p)
                  for p in path_parts[startpos:]]
    if len(last_parts) == 2:
        # If the path is 2 elements deep, output all as-is
        return tuple([ws_path] + last_parts)
    else:
        # Detect if the input path was a feature dataset or not and output accordingly
        try:
            meta = _arcpy.Describe(path)
            if not meta.dataType == 'FeatureDataset':
                raise ValueError
        except (RuntimeError, IOError, AttributeError, ValueError, NameError):
            return ws_path, _const.CHAR_EMPTY, last_parts[0]
        return ws_path, last_parts[0], _const.CHAR_EMPTY
コード例 #2
0
ファイル: cursors.py プロジェクト: geocom-gis/gpf
    def newRow(self, values=None):
        """
        Returns a new MutableRow instance (optionally populated with data).

        :param values:      Optional ``list`` or ``tuple`` of field values in the correct ``InsertCursor`` field order
                            or a ``dict`` of key-value pairs, where the keys specify the field names to set.
        :raises ValueError: If *values* is a ``list`` or ``tuple`` and the length does not match the number of
                            cursor fields, or if *values* is a ``dict`` and one of the keys does not match with the
                            cursor field names.
        :rtype:             _MutableRow
        """

        _vld.raise_if(values and not _vld.is_iterable(values), ValueError,
                      "newRow() 'values' should be iterable or None")

        # Although it would be more efficient to initialize _MutableRow once and simply call it
        # to set its values, this might not be what the user expects. Therefore, we (re)initialize it each time.
        if isinstance(values, dict):
            row = _MutableRow(self._field_map)
            for k, v in values.iteritems():
                row.setValue(k, v)
        else:
            row = _MutableRow(self._field_map)(values)

        return row
コード例 #3
0
ファイル: lookup.py プロジェクト: GeoSander/gpf
 def __init__(self, table_path, key_field, value_fields, where_clause=None, **kwargs):
     _vld.raise_if(len(value_fields) <= 1, ValueError, '{} expects multiple value fields: use {} instead'.format(
             RowLookup.__name__, ValueLookup.__name__))
     _vld.pass_if(all(_vld.has_value(v) for v in (table_path, key_field, value_fields[0])), ValueError,
                  '{} requires valid table_path, key_field and value_fields arguments')
     super(RowLookup, self).__init__(table_path, key_field, value_fields, where_clause, **kwargs)
     self._fieldmap = {name.lower(): i for i, name in enumerate(value_fields)}
コード例 #4
0
ファイル: lookup.py プロジェクト: GeoSander/gpf
 def __init__(self, table_path, key_field, value_field, where_clause=None, **kwargs):
     _vld.raise_if(_vld.is_iterable(value_field), ValueError,
                   '{} expects a single value field: use {} instead'.format(ValueLookup.__name__,
                                                                            RowLookup.__name__))
     _vld.pass_if(all(_vld.has_value(v) for v in (table_path, key_field, value_field)), ValueError,
                  '{} requires valid table_path, key_field and value_field arguments')
     super(ValueLookup, self).__init__(table_path, key_field, value_field, where_clause, **kwargs)
コード例 #5
0
def first(iterable, default=_const.EMPTY_OBJ):
    """Return the first item of *iterable*, or *default* if *iterable* is
    empty.

        >>> first([0, 1, 2, 3])
        0
        >>> first([], 'some default')
        'some default'

    If *default* is not provided and there are no items in the iterable,
    raise ``ValueError``.

    :func:`first` is useful when you have a generator of expensive-to-retrieve
    values and want any arbitrary one. It is marginally shorter than
    ``next(iter(iterable), default)``.

    .. note::    Function copied from `more_itertools` package (Python 3 built-in).
    """
    try:
        return next(iter(iterable))
    except StopIteration:
        _vld.raise_if(
            default is _const.EMPTY_OBJ, ValueError,
            'first() was called on an empty iterable, and no default value was provided.'
        )
        return default
コード例 #6
0
    def _add_expression(self, operator, *values):
        """
        Private method to properly complete an SQL expression for the initial field.
        The current internal SQL expression will be permanently changed.
        Explicit consecutive calls to this function will raise an ``OverflowError``.

        :param operator:    The operator to use (=, <>, IN etc.).
        :param values:      The value(s) following the operator.
        """
        _vld.raise_if(self._locked, OverflowError,
                      'Cannot add more than one expression to a field')

        operator = self._fix_operator(operator)
        is_between = operator.endswith(self.__SQL_BETWEEN)
        self._parts.append(operator.upper())

        if operator not in (self.__SQL_NULL, self.__SQL_NOT_NULL):
            values = self._fix_values(*values, check_only=is_between)
            if operator.endswith(self.__SQL_IN):
                self._parts.append('({})'.format(', '.join(values)))
            elif is_between:
                self._between(operator, *values)
            else:
                self._parts.append(_iter.first(values))

        self._locked = True
コード例 #7
0
    def __init__(self,
                 table_path: str,
                 key_field: str,
                 value_fields: _tp.Sequence[str],
                 where_clause: _tp.Union[None, str, _q.Where] = None,
                 **kwargs):
        _vld.raise_if(
            len(value_fields) <= 1, ValueError,
            f'{RowLookup.__name__} expects multiple value fields: use {ValueLookup.__name__} instead'
        )
        _vld.pass_if(
            all(
                _vld.has_value(v)
                for v in (table_path, key_field, value_fields[0])), ValueError,
            f'{RowLookup.__name__} requires valid table_path, key_field and value_fields arguments'
        )

        # User cannot override row processor function for this class
        if _ROWFUNC_ARG in kwargs:
            del kwargs[_ROWFUNC_ARG]

        self._dupekeys = kwargs.get(_DUPEKEYS_ARG, False)
        self._rowtype = list if kwargs.get(_MUTABLE_ARG, False) else tuple
        super(RowLookup, self).__init__(table_path, key_field, value_fields,
                                        where_clause, **kwargs)

        self._fieldmap = {
            name.lower(): i
            for i, name in enumerate(value_fields)
        }
コード例 #8
0
 def _between(self, operator, *values):
     """ Adds a BETWEEN .. AND .. expression to the SQL query. """
     num_values = len(values)
     _vld.raise_if(
         num_values < 2, OperatorError,
         '{} requires at least 2 values (got {})'.format(
             operator.upper(), num_values))
     lower, upper = (self._format_value(v)
                     for v in (min(values), max(values)))
     self._parts.extend([lower, self.__SQL_AND.upper(), upper])
コード例 #9
0
 def _set_attrs(self, attributes):
     """ Checks and fixes all attribute names. """
     out_attrs = []
     for attr_name in _iter.collapse(attributes):
         attr_name = self._fix_attr(attr_name).lower()
         _vld.raise_if(attr_name in self._blacklist, ValueError,
                       'Field name {!r} shadows built-in name'.format(attr_name))
         _vld.raise_if(attr_name in out_attrs, ValueError, 'Field names must be unique')
         out_attrs.append(attr_name)
     return tuple(out_attrs)
コード例 #10
0
ファイル: lookup.py プロジェクト: GeoSander/gpf
    def _get_fields(table_path):
        """
        Gets all field names in the table.

        :raises RuntimeError:   When the table metadata could not be retrieved.
        :rtype:                 list
        """
        desc = _metadata.Describe(table_path)
        _vld.raise_if(desc.error, RuntimeError,
                      'Failed to create lookup for {}: {}'.format(_tu.to_repr(table_path), desc.error))
        return desc.fields(True, True)
コード例 #11
0
ファイル: lookups.py プロジェクト: geocom-gis/gpf
    def __init__(self, table_path, key_field, value_field, where_clause=None, **kwargs):
        _vld.raise_if(_vld.is_iterable(value_field), ValueError,
                      '{} expects a single value field: use {} instead'.format(ValueLookup.__name__,
                                                                               RowLookup.__name__))
        _vld.pass_if(all(_vld.has_value(v) for v in (table_path, key_field, value_field)), ValueError,
                     '{} requires valid table_path, key_field and value_field arguments'.format(ValueLookup.__name__))

        # User cannot override row processor function for this class
        if _ROWFUNC_ARG in kwargs:
            del kwargs[_ROWFUNC_ARG]

        self._dupekeys = kwargs.get(_DUPEKEYS_ARG, False)
        super(ValueLookup, self).__init__(table_path, key_field, value_field, where_clause, **kwargs)
コード例 #12
0
ファイル: paths.py プロジェクト: geocom-gis/gpf3
    def __init__(self, path, base=None):
        _vld.pass_if(_vld.is_text(path, False), TypeError,
                     "Attribute 'path' should be a non-empty string")
        self._path = _os.path.normpath(path)

        if base:
            _vld.raise_if(
                _os.path.isabs(self._path), ValueError,
                f'{self.__class__.__name__} expects a relative path when root has been set'
            )
            self._path = get_abs(self._path, base)

        self._head, self._tail = _os.path.split(self._path)
        self._end, self._ext = _os.path.splitext(self._tail)
コード例 #13
0
    def _check_types(*args) -> bool:
        """ Checks that all query values have compatible data types. Applies to IN and BETWEEN operators. """

        # Check that none of the arguments are of type 'object' (this breaks the type check)
        _vld.raise_if(
            any(v.__class__ is object for v in args), ValueError,
            f'Values of type object are not allowed in IN and BETWEEN queries')

        # Get the first value and get its type
        sample_val = args[0]
        sample_type = type(sample_val)

        # Allow for some flexibility concerning numbers
        if _vld.is_number(sample_val, True):
            # For now, we will allow a mixture of floats and integers (and bools) in the list of values
            sample_type = (int, float, bool)

        return all(isinstance(v, sample_type) for v in args)
コード例 #14
0
ファイル: cursors.py プロジェクト: geocom-gis/gpf
    def getValue(self, field, default=_const.OBJ_EMPTY):
        """
        Returns the value that matches the given *field* name for the current row.

        A *default* can be specified in case the value was not found.

        :param field:       The (case-insensitive) name of the field for which to retrieve the value.
        :param default:     The default value to return in case the field was not found.
        :type field:        str, unicode
        :raise ValueError:  If *default* is omitted and a value cannot be found.
        :return:            A value (an Esri or Python basic type) or ``None``.
        """
        try:
            return self[self._fieldmap[field.upper()]]
        except (KeyError, IndexError):
            _vld.raise_if(default is _const.OBJ_EMPTY, ValueError,
                          'getValue() field {!r} does not exist and no default value was provided'.format(field))
            return default
コード例 #15
0
def get_bucket_class(field_names, writable=True):
    """
    Factory function to obtain a :class:`FrozenBucket` or a :class:`Bucket` container class.

    The function instantiates a :class:`BucketFactory` and calls its `get_bucket_class` method.

    :param field_names: An iterable of field names for which to create a bucket class.
    :param writable:    If ``False`` (default = ``True``), a :class:`FrozenBucket` type will be returned.
                        By default, a :class:`Bucket` type will be returned.
    :type field_names:  iterable
    :type writable:     bool
    :rtype:             type

    In theory, you could immediately instantiate the returned bucket class.
    This is okay for a single bucket, but considered bad practice if you do this consecutively (e.g. in a loop).
    For example, it's fine if you do this once:

        >>> my_bucket = get_bucket_class(['field1', 'field2'])(1, 2)
        >>> print(my_bucket)
        Bucket(field1=1, field2=2)

    However, if you need to reuse the bucket class to instantiate multiple buckets, this is better:

        >>> fields = ('Field-With-Dash', 'UPPERCASE_FIELD')
        >>> bucket_cls = get_bucket_class(fields, writable=False)
        >>> for i in range(3):
        >>>     print(bucket_cls(i, i+1))
        FrozenBucket(field_with_dash=0, uppercase_field=1)
        FrozenBucket(field_with_dash=1, uppercase_field=2)
        FrozenBucket(field_with_dash=2, uppercase_field=3)

    .. seealso::    :class:`BucketFactory`, :class:`Bucket`, :class:`FrozenBucket`
    """
    # Input validation
    _vld.raise_if(field_names == _tu.ASTERISK, NotImplementedError,
                  "{} does not support {!r} as 'field_names' attribute".format(get_bucket_class.__name__, _tu.ASTERISK))
    _vld.pass_if(_vld.is_iterable(field_names), TypeError, "'field_names' attribute must be an iterable")

    rec_fields = _iter.collapse(field_names, levels=1)
    return BucketFactory(rec_fields).get_bucket_class(writable)
コード例 #16
0
ファイル: paths.py プロジェクト: geocom-gis/gpf
    def make_path(self, *parts, **kwargs):
        """
        make_path(*parts, {qualifier}, {separator})

        Constructs a (qualified) path for the given named parts (data elements) in the order they appear.

        :param parts:           Feature dataset, feature class and/or table name(s) to concatenate.
                                Note that if the workspace is a FileSystem directory, the last part of `parts` should
                                include a file extension (e.g. '.shp').
        :keyword qualifier:     Optional qualifier if the one derived from the DB connection should be overridden.
        :keyword separator:     Optional separator if the initial one should be overridden (defaults to '.').
        :rtype:                 str, unicode
        :raises IndexError:     When more than 2 `parts` have been specified, this function will fail.

        In the following example, the qualifier ("user") is derived from the connection:

            >>> wm = Workspace(r'C:/temp/db_user.sde')
            >>> wm.qualifier
            'user'
            >>> wm.make_path('ele', 'ele_kabel')
            'C:\\temp\\db_user.sde\\user.ele\\user.ele_kabel'

        Using the ``Workspace`` above, we can override the qualifier with a custom one:

            >>> wm.make_path('ele', 'ele_kabel', qualifier='editor')
            'C:\\temp\\db_user.sde\\editor.ele\\editor.ele_kabel'

        """
        _vld.raise_if(
            len(parts) > 2, IndexError,
            "{}.make_path() cannot be called with more than 2 'parts' arguments"
            .format(Workspace.__name__))

        qualifier = kwargs.get(_ARG_QF, self._qualifier)
        separator = kwargs.get(_ARG_SEP, self._sep)
        self._map_fc(self._fds_lookup,
                     *parts)  # update the lookup, if necessary
        return self._make_path(qualifier, separator, *parts)
コード例 #17
0
    def _fix_values(self, *values, **kwargs):
        """
        Private method to validate *values* for use in an SQL expression.
        All values, regardless if they are iterables themselves, will be flattened (up to 1 level).
        If the values do not have comparable types (i.e. all numeric, all strings), a ``TypeError`` will be raised.

        :param values:          An iterable (of iterables) with values to use for the SQL expression.
        :keyword check_only:    When False (default=True), values will be sorted and duplicates will be removed.
                                Furthermore, the returned values will be formatted for the SQL expression.
                                When True, the values will only be flattened and checked for comparable types.
        :return:                A generator of checked (and formatted) values.
        """
        _vld.pass_if(
            values, OperatorError,
            'Specified {} operator requires at least one value'.format(
                Where.__name__))

        values = [v for v in _iter.collapse(values, levels=1)]
        unique_values = frozenset(values)
        first_val = _iter.first(unique_values)

        if _vld.is_number(first_val, True):
            # For now, allow a mixture of floats and integers (and bools) in the list of values
            # TODO: When input field is an arcpy.Field instance, filter by field.type
            first_type = (int, float, bool)
        elif _vld.is_text(first_val):
            first_type = (str, unicode)
        else:
            first_type = type(first_val)

        _vld.raise_if(
            any(not isinstance(v, first_type) for v in unique_values),
            TypeError, 'All {} values must have the same data type'.format(
                Where.__name__))

        check_only = kwargs.get(_CHECK_ARG, False)
        return (v if check_only else self._format_value(v)
                for v in (values if check_only else sorted(unique_values)))
コード例 #18
0
ファイル: queries.py プロジェクト: geocom-gis/gpf
 def _output(self, check=False):
     """ Concatenates all query parts to form an actual SQL expression. Can check if the query is dirty. """
     _vld.raise_if(check and self._isdirty, ValueError,
                   'Cannot output invalid query')
     return u"""{}""".format(
         _const.CHAR_SPACE.join(part for part, _ in self._parts))
コード例 #19
0
def test_raiseif():
    with pytest.raises(TypeError):
        validate.raise_if(True is True, 'bad', 'test')
    with pytest.raises(TypeError):
        validate.raise_if(True is True, TypeError, 'type fail')
    validate.raise_if(True is False, Exception, 'pass if False')