Skip to content

FeatureClass

CLASS DESCRIPTION
AttributeRuleManager

Handler for interacting with AttributeRules on a FeatureClass or Table

FeatureClass

A Wrapper for ArcGIS FeatureClass objects

Table

A Wrapper for ArcGIS Table objects

FUNCTION DESCRIPTION
as_dict

Take a Cusrsor object and yield rows from it

count

Get the record count of a FeatureClass

extract_singleton

Helper function to allow passing single values to arguments that expect a tuple

filter_fields

Decorator for filter functions that limits fields checked by the SearchCursor

format_query_list

Format a list of values into a SQL list

norm

Normalize a value for SQL query (wrap strings in single quotes)

valid_field

Validate a fieldname

where

Wrap a string in a WhereClause object to use with indexing

ATTRIBUTE DESCRIPTION
FieldName

Alias for string that specifies the function needs a valid fieldname

FilterFunc

The expected type signature for function indexing

RowRecord

Alias for a dictionary of fieldnames and field values

FilterFunc module-attribute

FilterFunc = Callable[[_Schema], bool]

The expected type signature for function indexing

RowRecord module-attribute

RowRecord = dict[FieldName, Any]

Alias for a dictionary of fieldnames and field values

AttributeRuleManager

AttributeRuleManager(parent: Table[Any] | FeatureClass)

Handler for interacting with AttributeRules on a FeatureClass or Table

METHOD DESCRIPTION
__setitem__

The primary method for interacting with attribute rules

delete_attribute_rule

Delete provided attribute rules from the ruleset

disable_attribute_rule

Disable provided attribute rules from the ruleset

enable_attribute_rule

Enable provided attribute rules in the ruleset

export_rules

Write attribute rules out to a structured directory

import_rules

Import attribute rules that were previously exported to the filesystem for editing

sync

Sync the rules in this FeatureClass/Table instance with those of another overwriting

Source code in src/arcpie/featureclass.py
2161
2162
def __init__(self, parent: Table[Any]|FeatureClass) -> None:
    self._parent = parent

__setitem__

__setitem__(
    rule_name: str, new_rule: AttributeRule
) -> None

The primary method for interacting with attribute rules

The setitem override will take any dictionary that contains the keys expected by the AttributeRule definition. Alteration or Addition is determined and applied depending on the name of the rule and its state compared to the matching rule in the current ruleset.

Example
>>> fc.attribute_rules.names
['Rule A', 'Rule B']
>>> fc.attribute_rules['Rule A'] = {'isEnabled': False}
Source code in src/arcpie/featureclass.py
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
def __setitem__(self, rule_name: str, new_rule: AttributeRule) -> None:
    """The primary method for interacting with attribute rules

    The setitem override will take any dictionary that contains the keys expected by 
    the `AttributeRule` definition. Alteration or Addition is determined and applied 
    depending on the name of the rule and its state compared to the matching rule in 
    the current ruleset.

    Example:
        ```python
        >>> fc.attribute_rules.names
        ['Rule A', 'Rule B']
        >>> fc.attribute_rules['Rule A'] = {'isEnabled': False}
        ```
    """
    new_rule['name'] = rule_name
    current_rule = self.get(rule_name)
    is_enabled = new_rule.get('isEnabled', True)

    # Skip fields that are modified by the system
    skip_compare = {
        'id',
        'type',
        'requiredGeodatabaseClientVersion',
        'creationTime',
    }

    # Add a new rule
    if not current_rule:
        self.add_attribute_rule(**to_rule_add(new_rule))
        if not is_enabled:
            self.disable_attribute_rule(rule_name)
        return

    # Enable/Disable
    if is_enabled and not current_rule['isEnabled']:
        self.enable_attribute_rule(rule_name)
    elif not is_enabled and current_rule['isEnabled']:
        self.disable_attribute_rule(rule_name)
    is_enabled = current_rule['isEnabled']

    # Get Changes
    changes: dict[str, Any] = {
        setting: new_rule[setting]
        for setting in current_rule 
        if setting not in skip_compare
        and setting in new_rule
        and new_rule[setting] != current_rule[setting]
    }

    if not changes:
        return

    # Subtype change requires a re-build
    if 'subtypeCodes'in changes:
        self.delete_attribute_rule(rule_name)
        current_rule.update(new_rule)
        self.add_attribute_rule(**to_rule_add(current_rule))
    else:
        self.alter_attribute_rule(
            evaluation_order=changes.get('evaluatonOrder'),
            **to_rule_alter(new_rule)
        )

delete_attribute_rule

delete_attribute_rule(
    *rule_name: str, delete_all: bool = False
) -> None

Delete provided attribute rules from the ruleset

PARAMETER DESCRIPTION

*rule_name

The rule names to delete as positional varargs

TYPE: str DEFAULT: ()

delete_all

If this flag is set, the noarg case will delete all rules (default: False)

TYPE: bool DEFAULT: False

Source code in src/arcpie/featureclass.py
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
def delete_attribute_rule(self, *rule_name: str, delete_all: bool=False) -> None:
    """Delete provided attribute rules from the ruleset

    Args:
        *rule_name (str): The rule names to delete as positional varargs
        delete_all (bool): If this flag is set, the noarg case will delete all rules (default: False)
    """
    if not rule_name and delete_all:
        rule_name = tuple(self.names)
    DeleteAttributeRule(str(self.parent), rule_name)

disable_attribute_rule

disable_attribute_rule(
    *rule_name: str, disable_all: bool = False
) -> None

Disable provided attribute rules from the ruleset

PARAMETER DESCRIPTION

*rule_name

The rule names to delete as positional varargs

TYPE: str DEFAULT: ()

disable_all

If this flag is set, the noarg case will disable all rules (default: False)

TYPE: bool DEFAULT: False

Source code in src/arcpie/featureclass.py
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
def disable_attribute_rule(self, *rule_name: str, disable_all: bool=False) -> None:
    """Disable provided attribute rules from the ruleset

    Args:
        *rule_name (str): The rule names to delete as positional varargs
        disable_all (bool): If this flag is set, the noarg case will disable all rules (default: False)
    """
    if not rule_name and disable_all:
        rule_name = tuple(self.names)
    DisableAttributeRules(str(self.parent), rule_name)

enable_attribute_rule

enable_attribute_rule(
    *rule_name: str, enable_all: bool = False
) -> None

Enable provided attribute rules in the ruleset

PARAMETER DESCRIPTION

*rule_name

The rule names to delete as positional varargs

TYPE: str DEFAULT: ()

enable_all

If this flag is set, the noarg case will enable all rules (default: False)

TYPE: bool DEFAULT: False

Source code in src/arcpie/featureclass.py
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
def enable_attribute_rule(self, *rule_name: str, enable_all: bool=False) -> None:
    """Enable provided attribute rules in the ruleset

    Args:
        *rule_name (str): The rule names to delete as positional varargs
        enable_all (bool): If this flag is set, the noarg case will enable all rules (default: False)
    """
    if not rule_name and enable_all:
        rule_name = tuple(self.names)
    EnableAttributeRules(str(self.parent), rule_name)

export_rules

export_rules(
    out_dir: Path | str,
) -> Iterator[AttributeRule]

Write attribute rules out to a structured directory

PARAMETER DESCRIPTION

out_dir

The target directory to dump all attribute rules and configs to

TYPE: Path | str

Note

out_dir -> fc_name -> [rule_name.cfg, rule_name.js]

Source code in src/arcpie/featureclass.py
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
def export_rules(self, out_dir: Path|str) -> Iterator[AttributeRule]:
    """Write attribute rules out to a structured directory

    Args:
        out_dir (Path|str): The target directory to dump all attribute rules and configs to

    Note:
        out_dir -> fc_name -> [rule_name.cfg, rule_name.js]
    """
    out_dir = Path(out_dir)
    for rule_name, rule in self.rules.items():
        rule_name = rule_name.replace('/', '-') # Arc allows / in rulenames
        _script: str = str(rule.pop('scriptExpression', '')) # TypedDict has bugged pop typing
        out_file = out_dir / self._parent.name / rule_name
        out_file.parent.mkdir(exist_ok=True, parents=True)
        out_file.with_suffix('.js').write_text(_script, encoding='utf-8')
        out_file.with_suffix('.cfg').write_text(json.dumps(rule, indent=2), encoding='utf-8')
        yield rule
    return

import_rules

import_rules(
    src_dir: Path | str,
    *,
    strict: bool = False,
    disable: bool = False,
) -> Iterator[AttributeRule]

Import attribute rules that were previously exported to the filesystem for editing

PARAMETER DESCRIPTION

src_dir

The directory that contains the .cfg and .js files for each rule

TYPE: Path | str

strict

Delete any attribute rules in the FeatureClass that do not have a matching file (default: False)

TYPE: bool DEFAULT: False

disable

Disable any attribute rules in the FeatureClass that do not have a matching file (default: False)

TYPE: bool DEFAULT: False

Note

the disable option will be ignored if strict is not set

Source code in src/arcpie/featureclass.py
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
def import_rules(self, src_dir: Path|str, *, strict: bool=False, disable: bool=False) -> Iterator[AttributeRule]:
    """Import attribute rules that were previously exported to the filesystem for editing

    Args:
        src_dir (Path|str): The directory that contains the `.cfg` and `.js` files for each rule
        strict (bool): Delete any attribute rules in the FeatureClass that do not have a matching file (default: False)
        disable (bool): Disable any attribute rules in the FeatureClass that do not have a matching file (default: False)

    Note:
        the `disable` option will be ignored if strict is not set
    """
    # Ensure that only the directory for the parent FC is accessed
    src_dir = Path(src_dir)
    if src_dir.stem != self.parent.name:
        src_dir = src_dir / self.parent.name

    _old_rules = {k: v.copy() for k,v in self.rules.items()}
    _imported_rule_names: set[str] = set()
    rule_config: AttributeRule = {'name': 'UNINITIALIZED'} # type: ignore
    try:
        rule_orders: dict[str, int] = {}
        for cfg in src_dir.glob('*.cfg'):
            # Grab base config and attach script sidecar
            rule_config: AttributeRule = json.loads(cfg.read_text(encoding='utf-8'))
            rule_script = cfg.with_suffix('.js').read_text(encoding='utf-8')
            rule = rule_config.copy()
            rule['scriptExpression'] = rule_script

            # Let the __setitem__ logic handle the rule (alter/add)
            self[rule['name']] = rule
            _imported_rule_names.add(rule['name'])

            # Store order for re-ordering later
            rule_orders[rule['name']] = rule['evaluationOrder']

        # Re-Order added rules if they don't match
        for rule_name, rule_order in sorted(rule_orders.items(), key=lambda i: i[1]):
            if rule_name not in self.rules:
                continue
            if self.rules[rule_name]['evaluationOrder'] != rule_order:
                self.alter_attribute_rule(name=rule_name, evaluation_order=rule_order)

        if strict and (to_remove := set(self.names).difference(_imported_rule_names)):
            if disable:
                self.disable_attribute_rule(*to_remove)
            else:
                self.delete_attribute_rule(*to_remove)
        yield from (self[rule] for rule in rule_orders)

    except Exception as e:
        # Revert the import if an Exception is rasied
        for rule_name, rule in _old_rules.items():
            if rule_name in _imported_rule_names:
                self[rule_name] = rule

        # Remove rules
        if (to_remove := set(_old_rules).difference(self.names)):
            self.delete_attribute_rule(*to_remove)

        e.add_note(f"{rule_config['name']} failed to import")
        e.add_note(f'Config: {pformat(convert_rule(rule_config))}')
        e.add_note(f'Transaction reverted for {_imported_rule_names} in {self.parent.name}')
        raise e # Raise the Exception

sync

sync(target: FeatureClass | Table) -> None

Sync the rules in this FeatureClass/Table instance with those of another overwriting the current ruleset with the targeted ruleset

PARAMETER DESCRIPTION

target

The target ruleset to overwrite the current rules with

TYPE: FeatureClass | Table

Source code in src/arcpie/featureclass.py
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
def sync(self, target: FeatureClass|Table) -> None:
    """Sync the rules in this FeatureClass/Table instance with those of another overwriting 
    the current ruleset with the targeted ruleset

    Args:
        target (FeatureClass|Table): The target ruleset to overwrite the current rules with
    """
    # Use existing import functionality
    with TemporaryDirectory() as temp:
        target.attribute_rules.export_rules(temp)
        self.import_rules(temp)

FeatureClass

FeatureClass(
    path: str | Path,
    *,
    search_options: SearchOptions | None = None,
    update_options: UpdateOptions | None = None,
    insert_options: InsertOptions | None = None,
    clause: SQLClause | None = None,
    where: str | None = None,
    shape_token: ShapeToken = "SHAPE@",
)

Bases: Table[_Schema], Generic[_GeometryType, _Schema]

A Wrapper for ArcGIS FeatureClass objects

Example
>>> # Initialize FeatureClass with Geometry Type
>>> point_features = FeatureClass[PointGeometry]('<feature_class_path>')
>>> # Create a buffer Iterator
>>> buffers = (pt.buffer(10) for pt in point_features.shapes)
... 
>>> sr = SpatialReference(4206)
>>> # Set a new spatial reference
>>> with point_features.reference_as(sr):
...     # Consume the Iterator, but with the new reference
...     for buffer in buffers:
...        area = buffer.area
...        units = sr.linearUnitName
...        print(f"{area} Sq{units}")
METHOD DESCRIPTION
__contains__

Implementation of contains that checks for a field existing in the FeatureClass

__eq__

Determine if the datasource of two featureclass objects is the same

__getitem__

Handle all defined overloads using pattern matching syntax

__iter__

Iterate all rows in the Table or FeatureClass yielding mappings of field name to field value

__len__

Iterate all rows and count them. Only count with self.search_options queries.

__repr__

Provide a constructor string e.g. Table or FeatureClass[Polygon]('path')

__str__

Return the Table or FeatureClass path for use with other arcpy methods

add_field

Add a new field to a Table or FeatureClass, if no type is provided, deafault of VARCHAR(255) is used

add_fields

Provide a mapping of fieldnames to Fields

add_to_map

Add the featureclass to a map

bind_to_layer

Update the provided layer's datasource to this Table or FeatureClass

clear

Clear all records from the table

copy

Create a new FeatureClass instance to prevent overriding a shared resource

copy_to

Copy this Table or FeatureClass to a new workspace

create_annotations

Create an AnnotationClass for the Features (requires a linked Layer object)

delete

Delete the object permanently using arcpy.management.Delete

delete_field

Delete a field from a Table or FeatureClass

delete_identical

Delete all records that have matching field values

delete_where

Delete all records that match the provided where clause

distinct

Yield rows of distinct values

exists

Check if the Table or FeatureClass actually exists (check for deletion or initialization with bad path)

fields_as

Override the default fields for the Table or FeatureClass so all non-explicit Iterators will

filter

Apply a function filter to rows in the Table or FeatureClass

footprint

Merge all geometry in the featureclass using current SelectionOptions into a single geometry object to use

from_layer

Build a FeatureClass object from a layer applying the layer's current selection to the stored cursors

from_table

See from_layer for documentation, this is an alternative constructor that builds from a mp.Table object

get

Allows safe indexing of a FeatureClass, see Table.get for more information

get_records

Generate row dicts with in the form {field: value, ...} for each row in the cursor

get_schema

Get python code for the Table/FeatureClass schema

get_transformation

Get the name of the transformation to convert from feature reference to provided reference

get_tuples

Generate tuple rows in the for (val1, val2, ...) for each row in the cursor

group_by

Group features by matching field values and yield full records in groups

has_field

Check if the field exists in the featureclass or is a valid Token (@[TOKEN])

insert_cursor

See Table.search_cursor doc for general info. Operation of this method is identical but returns an InsertCursor

insert_record

Insert a single record into the table

insert_records

Provide an iterable of records to insert

is_empty

Check if a Table/FeatureClass is empty

options

Enter a context block where the supplied options replace the stored options for the Table or FeatureClass

recalculate_extent

Recalculate the FeatureClass Extent

reference_as

Allows you to temporarily set a spatial reference on SearchCursor and UpdateCursor objects within a context block

row_updater

A Bi-Directional generator that yields rows and updates them with the sent value

search_cursor

Get a SearchCursor for the Table or FeatureClass

select

If the Table or FeatureClass is bound to a layer, update the layer selection with the active SearchOptions

spatial_filter

Apply a spatial filter to the FeatureClass in a context

unselect

If the Table or FeatureClass is bound to a layer, Remove layer selection

update_cursor

See Table.search_cursor doc for general info. Operation of this method is identical but returns an UpdateCursor

updater

A wrapper around row_updater that allows use as a context manager

where

Apply a where clause to a Table or FeatureClass in a context

ATTRIBUTE DESCRIPTION
attribute_rules

Get an AttributeRuleManager object bound to the Table/FeatureClass

TYPE: AttributeRuleManager

clause

Default SQLClause

TYPE: SQLClause

current_reference

The CURRENT SpatialReference object for the FeatureClass (using reference_as will update this value)

TYPE: SpatialReference

da_describe

Access the da.Describe dictionary for the Table or FeatureClass

TYPE: dict[str, Any]

describe

A describe object fort the FeatureClass

TYPE: FeatureClass

editor

Get an Editor manager for the Table or FeatureClass

TYPE: Editor

extent

Get the stored extent of the FeatureClass

TYPE: Extent

field_defs

Get a mapping of Field properties to fieldnames

TYPE: dict[FieldName, Field]

fields

Tuple of all fieldnames in the FeatureClass with OID@ and SHAPE@ as first 2

TYPE: tuple[FieldName | FeatureToken, ...]

insert_options

Default InsertCursor options

TYPE: InsertOptions

is_currently_geographic

True if the features are CURRENTLY in a Geographic coordiante system (respects reference_as)

TYPE: bool

is_geographic

True if the features are in a Geographic coordinate system

TYPE: bool

layer

A Layer object for the FeatureClass/Table if one is bound

TYPE: Layer | None

name

The common name of the FeatureClass/Table

TYPE: str

np_dtypes

Numpy dtypes for each field

oid_field_name

ObjectID fieldname (ususally FID or OID or ObjectID)

TYPE: str

parent

The parent of the Table/FeatureClass (either a Dataset or a Database)

TYPE: str

path

The filepath of the FeatureClass/Table

TYPE: str

py_types

Get a mapping of the field types for the FeatureClass

TYPE: dict[str, type]

search_options

Default SearchCursor options

TYPE: SearchOptions

shape_extent

Get a new extent by finding the maximum extent of the current shapes.

TYPE: Extent | None

shape_field_name

The name for the base shape field of the FeatureClass

TYPE: str

shape_token

Set the default SHAPE@?? token for iteration. Use SHAPE@ for full shape (default: SHAPE@)

TYPE: ShapeToken

shapes

An iterator of feature shapes

TYPE: Iterator[_GeometryType]

spatial_reference

The SpatialReference object for the FeatureClass

TYPE: SpatialReference

subtype_field

The Subtype field (ususally SUBTYPE or SUBTYPE_CODE, etc.)

TYPE: str | None

subtypes

Result of ListSubtypes, mapping of code to Subtype object

TYPE: dict[int, Subtype]

units

The unit name of the FeatureClass

TYPE: str

update_options

Default UpdateCursor options

TYPE: UpdateOptions

workspace

Get the workspace of the Table or FeatureClass

TYPE: str

Source code in src/arcpie/featureclass.py
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
def __init__(
        self, path: str|Path,
        *,
        search_options: SearchOptions|None=None, 
        update_options: UpdateOptions|None=None, 
        insert_options: InsertOptions|None=None,
        clause: SQLClause|None=None,
        where: str|None=None,
        shape_token: ShapeToken='SHAPE@'
    ) -> None:
    super().__init__(
        path=path, 
        search_options=search_options, update_options=update_options, insert_options=insert_options, 
        clause=clause, where=where
    )
    self._shape_token: ShapeToken = shape_token

attribute_rules property

attribute_rules: AttributeRuleManager

Get an AttributeRuleManager object bound to the Table/FeatureClass

clause property writable

clause: SQLClause

Default SQLClause

current_reference property

current_reference: SpatialReference

The CURRENT SpatialReference object for the FeatureClass (using reference_as will update this value)

da_describe property

da_describe: dict[str, Any]

Access the da.Describe dictionary for the Table or FeatureClass

describe property

describe: FeatureClass

A describe object fort the FeatureClass

editor property

editor: Editor

Get an Editor manager for the Table or FeatureClass Will set multiuser_mode to True if the feature can version

extent property

extent: Extent

Get the stored extent of the FeatureClass

field_defs property

field_defs: dict[FieldName, Field]

Get a mapping of Field properties to fieldnames

fields property

fields: tuple[FieldName | FeatureToken, ...]

Tuple of all fieldnames in the FeatureClass with OID@ and SHAPE@ as first 2

insert_options property writable

insert_options: InsertOptions

Default InsertCursor options

is_currently_geographic property

is_currently_geographic: bool

True if the features are CURRENTLY in a Geographic coordiante system (respects reference_as)

is_geographic property

is_geographic: bool

True if the features are in a Geographic coordinate system

layer property writable

layer: Layer | None

A Layer object for the FeatureClass/Table if one is bound

name property

name: str

The common name of the FeatureClass/Table

np_dtypes property

np_dtypes

Numpy dtypes for each field

oid_field_name property

oid_field_name: str

ObjectID fieldname (ususally FID or OID or ObjectID)

parent property

parent: str

The parent of the Table/FeatureClass (either a Dataset or a Database)

path property

path: str

The filepath of the FeatureClass/Table

py_types property

py_types: dict[str, type]

Get a mapping of the field types for the FeatureClass

search_options property writable

search_options: SearchOptions

Default SearchCursor options

shape_extent property

shape_extent: Extent | None

Get a new extent by finding the maximum extent of the current shapes.

If no features, None is returned will respect the spatial reference applied in a context manager (inherit ref from shapes)

shape_field_name property

shape_field_name: str

The name for the base shape field of the FeatureClass

shape_token property writable

shape_token: ShapeToken

Set the default SHAPE@?? token for iteration. Use SHAPE@ for full shape (default: SHAPE@)

shapes property

shapes: Iterator[_GeometryType]

An iterator of feature shapes

spatial_reference property

spatial_reference: SpatialReference

The SpatialReference object for the FeatureClass

subtype_field property

subtype_field: str | None

The Subtype field (ususally SUBTYPE or SUBTYPE_CODE, etc.)

subtypes property

subtypes: dict[int, Subtype]

Result of ListSubtypes, mapping of code to Subtype object

units property

units: str

The unit name of the FeatureClass

update_options property writable

update_options: UpdateOptions

Default UpdateCursor options

workspace property

workspace: str

Get the workspace of the Table or FeatureClass

__contains__

__contains__(field: str) -> bool

Implementation of contains that checks for a field existing in the FeatureClass

Source code in src/arcpie/featureclass.py
1191
1192
1193
1194
def __contains__(self, field: str) -> bool:
    """Implementation of contains that checks for a field existing in the `FeatureClass`
    """
    return field in self.fields

__eq__

__eq__(other: Any) -> bool

Determine if the datasource of two featureclass objects is the same

Source code in src/arcpie/featureclass.py
1244
1245
1246
def __eq__(self, other: Any) -> bool:
    """Determine if the datasource of two featureclass objects is the same"""
    return isinstance(other, self.__class__) and self.__fspath__() == other.__fspath__()

__getitem__

__getitem__(
    field: tuple[FieldName, ...],
) -> Iterator[tuple[Any, ...]]
__getitem__(field: list[FieldName]) -> Iterator[list[Any]]
__getitem__(field: set[FieldName]) -> Iterator[_Schema]
__getitem__(
    field: Literal["SHAPE@"],
) -> Iterator[_GeometryType]
__getitem__(field: FieldName) -> Iterator[Any]
__getitem__(
    field: FilterFunc[_Schema],
) -> Iterator[_Schema]
__getitem__(field: WhereClause) -> Iterator[_Schema]
__getitem__(field: None) -> Iterator[None]
__getitem__(
    field: GeometryType | Extent,
) -> Iterator[_Schema]
__getitem__(
    field: _IndexableTypes
    | FilterFunc[_Schema]
    | Extent
    | GeometryType
    | Literal["SHAPE@"],
) -> Iterator[Any]

Handle all defined overloads using pattern matching syntax

PARAMETER DESCRIPTION

field

Yield values in the specified column (values only)

TYPE: str

field

Yield lists of values for requested columns (requested fields)

TYPE: list[str]

field

Yield tuples of values for requested columns (requested fields)

TYPE: tuple[str]

field

Yield dictionaries of values for requested columns (requested fields)

TYPE: set[str]

field

Yield dictionaries of values for all features intersecting the specified shape

TYPE: Geometry | Extent

field

Yield rows that match function (all fields)

TYPE: FilterFunc

field

Yield rows that match clause (all fields)

TYPE: WhereClause

Example
>>> # Single Field
>>> print(list(fc['field']))
[val1, val2, val3, ...]

>>> # Field Tuple
>>> print(list(fc[('field1', 'field2')]))
[(val1, val2), (val1, val2), ...]

>>> # Field List
>>> print(list(fc[['field1', 'field2']]))
[[val1, val2], [val1, val2], ...]

>>> # Field Set (Row mapping limited to only requested fields)
>>> print(list(fc[{'field1', 'field2'}]))
[{'field1': val1, 'field2': val2}, {'field1': val1, 'field2': val2}, ...]

>>> # Last two options always return all fields in a mapping
>>> # Filter Function (passed to FeatureClass.filter())
>>> print(list(fc[lambda r: r['field1'] == target]))
[{'field1': val1, 'field2': val2, ...}, {'field1': val1, 'field2': val2, ...}, ...]

>>> # Where Clause (Use where() helper function or a WhereClause object)
>>> print(list(fc[where('field1 = target')]))
[{'field1': val1, 'field2': val2, ...}, {'field1': val1, 'field2': val2, ...}, ...]

>>> # Shape Filter (provide a shape to use as a spatial filter on the rows)
>>> print(list(fc[shape]))
[{'field1': val1, 'field2': val2, ...}, {'field1': val1, 'field2': val2, ...}, ...]

>>> # None (Empty Iterator)
>>> print(list(fc[None]))
Source code in src/arcpie/featureclass.py
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
def __getitem__(self, field: Table._IndexableTypes | FilterFunc[_Schema] | Extent | GeometryType | Literal['SHAPE@']) -> Iterator[Any]: # pyright: ignore[reportIncompatibleMethodOverride]
    """Handle all defined overloads using pattern matching syntax

    Args:
        field (str): Yield values in the specified column (values only)
        field (list[str]): Yield lists of values for requested columns (requested fields)
        field (tuple[str]): Yield tuples of values for requested columns (requested fields)
        field (set[str]): Yield dictionaries of values for requested columns (requested fields)
        field (Geometry | Extent): Yield dictionaries of values for all features intersecting the specified shape
        field (FilterFunc): Yield rows that match function (all fields)
        field (WhereClause): Yield rows that match clause (all fields)

    Example:
        ```python
        >>> # Single Field
        >>> print(list(fc['field']))
        [val1, val2, val3, ...]

        >>> # Field Tuple
        >>> print(list(fc[('field1', 'field2')]))
        [(val1, val2), (val1, val2), ...]

        >>> # Field List
        >>> print(list(fc[['field1', 'field2']]))
        [[val1, val2], [val1, val2], ...]

        >>> # Field Set (Row mapping limited to only requested fields)
        >>> print(list(fc[{'field1', 'field2'}]))
        [{'field1': val1, 'field2': val2}, {'field1': val1, 'field2': val2}, ...]

        >>> # Last two options always return all fields in a mapping
        >>> # Filter Function (passed to FeatureClass.filter())
        >>> print(list(fc[lambda r: r['field1'] == target]))
        [{'field1': val1, 'field2': val2, ...}, {'field1': val1, 'field2': val2, ...}, ...]

        >>> # Where Clause (Use where() helper function or a WhereClause object)
        >>> print(list(fc[where('field1 = target')]))
        [{'field1': val1, 'field2': val2, ...}, {'field1': val1, 'field2': val2, ...}, ...]

        >>> # Shape Filter (provide a shape to use as a spatial filter on the rows)
        >>> print(list(fc[shape]))
        [{'field1': val1, 'field2': val2, ...}, {'field1': val1, 'field2': val2, ...}, ...]

        >>> # None (Empty Iterator)
        >>> print(list(fc[None]))
        ```
    """
    match field:
        case 'SHAPE@':
            yield from self.shapes
        case shape if isinstance(shape, Extent | GeometryType):
            with self.search_cursor(*self.fields, spatial_filter=shape) as cur:
                yield from (row for row in self.as_dict(cur))
        case field if isinstance(field, str|set|list|tuple|Callable|WhereClause|None):
            yield from super().__getitem__(field)
        case _:
            raise KeyError(f'{type(field)}: {field}')

__iter__

__iter__() -> Iterator[_Schema]

Iterate all rows in the Table or FeatureClass yielding mappings of field name to field value

Note

It was decided to yield mappings because without specifying fields, it is up to the user to deal with the data as they see fit. Yielding tuples in an order that's not defined by the user would be confusing, so a mapping makes it clear exactly what they're accessing

Note

When a single field is specified using the fields_as context, values will be yielded

Source code in src/arcpie/featureclass.py
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
def __iter__(self) -> Iterator[_Schema]:
    """Iterate all rows in the Table or FeatureClass yielding mappings of field name to field value

    Note:
        It was decided to yield mappings because without specifying fields, it is up to the user
        to deal with the data as they see fit. Yielding tuples in an order that's not defined by
        the user would be confusing, so a mapping makes it clear exactly what they're accessing

    Note:
        When a single field is specified using the `fields_as` context, values will be yielded
    """ 
    with self.search_cursor(*self.fields) as cur:
        if len(self.fields) == 1:
            yield from (row for row, in cur)
        else:
            yield from self.as_dict(cur)

__len__

__len__() -> int

Iterate all rows and count them. Only count with self.search_options queries.

Note

The __format__('len') spec calls this function. So len(fc) and f'{fc:len}' are the same, with the caveat that the format spec option returns a string

Warning

This operation will traverse the whole dataset when called! You should not use it in loops:

# Bad
for i, _ in enumerate(fc):
    print(f'{i}/{len(fc)}')

# Good
count = len(fc)
for i, _ in enumerate(fc):
    print(f'{i}/{count}')

Source code in src/arcpie/featureclass.py
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
def __len__(self) -> int:
    """Iterate all rows and count them. Only count with `self.search_options` queries.

    Note:
        The `__format__('len')` spec calls this function. So `len(fc)` and `f'{fc:len}'` are the same, 
        with the caveat that the format spec option returns a string

    Warning:
        This operation will traverse the whole dataset when called! You should not use it in loops:
        ```python
        # Bad
        for i, _ in enumerate(fc):
            print(f'{i}/{len(fc)}')

        # Good
        count = len(fc)
        for i, _ in enumerate(fc):
            print(f'{i}/{count}')
        ```
    """
    #return sum(1 for _ in self['OID@'])
    return sum(1 for _ in self.search_cursor('OID@'))

__repr__

__repr__() -> str

Provide a constructor string e.g. Table or FeatureClass[Polygon]('path')

Source code in src/arcpie/featureclass.py
1236
1237
1238
def __repr__(self) -> str:
    """Provide a constructor string e.g. `Table or FeatureClass[Polygon]('path')`"""
    return f"{self.__class__.__name__}('{self.__fspath__()}')"

__str__

__str__() -> str

Return the Table or FeatureClass path for use with other arcpy methods

Source code in src/arcpie/featureclass.py
1240
1241
1242
def __str__(self) -> str:
    """Return the `Table` or `FeatureClass` path for use with other arcpy methods"""
    return self.__fspath__()

add_field

add_field(
    fieldname: str,
    field: Field | None = None,
    **options: Unpack[Field],
) -> None

Add a new field to a Table or FeatureClass, if no type is provided, deafault of VARCHAR(255) is used

PARAMETER DESCRIPTION

fieldname

The name of the new field (must not start with a number and be alphanum or underscored)

TYPE: str

field

A Field object that contains the desired field properties

TYPE: Field DEFAULT: None

**options

Allow passing keyword arguments for field directly (Overrides field arg)

TYPE: **Field DEFAULT: {}

Example
>>> new_field = Field(
...     field_alias='Abbreviated Month',
...     field_type='TEXT',
...     field_length='3',
...     field_domain='Months_ABBR',
... )

>>> print(fc.fields)
['OID@', 'SHAPE@', 'name', 'year']

>>> fc['month'] = new_field
>>> fc2['month'] = new_field # Can re-use a field definition 
>>> print(fc.fields)
['OID@', 'SHAPE@', 'name', 'year', 'month']
Source code in src/arcpie/featureclass.py
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
def add_field(self, fieldname: str, field: Field|None=None, **options: Unpack[Field]) -> None:
    """Add a new field to a Table or FeatureClass, if no type is provided, deafault of `VARCHAR(255)` is used

    Args:
        fieldname (str): The name of the new field (must not start with a number and be alphanum or underscored)
        field (Field): A Field object that contains the desired field properties
        **options (**Field): Allow passing keyword arguments for field directly (Overrides field arg)

    Example:
        ```python
        >>> new_field = Field(
        ...     field_alias='Abbreviated Month',
        ...     field_type='TEXT',
        ...     field_length='3',
        ...     field_domain='Months_ABBR',
        ... )

        >>> print(fc.fields)
        ['OID@', 'SHAPE@', 'name', 'year']

        >>> fc['month'] = new_field
        >>> fc2['month'] = new_field # Can re-use a field definition 
        >>> print(fc.fields)
        ['OID@', 'SHAPE@', 'name', 'year', 'month']
        ```
    """
    if self.has_field(fieldname):
        raise ValueError(f'{self.name} already has a field called {fieldname}!')

    # Use provided field or default to 'TEXT' and override with kwargs
    field = {**(field or Field(field_type='TEXT')), **options}

    # Handle malformed Field arg
    field['field_type'] = field.get('field_type', 'TEXT')

    _option_kwargs = set(Field.__optional_keys__) | set(Field.__required_keys__)
    _provided = set(field.keys())

    if not _provided <= _option_kwargs:
        raise ValueError(f"Unknown Field properties provided: {_provided - _option_kwargs}")

    if not valid_field(fieldname):
        raise ValueError(
            f"{fieldname} is invalid, fieldnames must not start with a number "
            "and must only contain alphanumeric characters and underscores"
        )

    default = field.pop('field_default') if 'field_default' in field else None        
    with EnvManager(workspace=self.workspace):
        AddField(self.path, fieldname, **field) # type: ignore (field_default is popped for alteration)
        self._fields = None
        if default is not None:
            AssignDefaultToField(self.path, fieldname, default_value=default)

add_fields

add_fields(fields: dict[str, Field]) -> None

Provide a mapping of fieldnames to Fields

PARAMETER DESCRIPTION

fields

A mapping of fieldnames to Field objects

TYPE: dict[str, Field]

Example
>>> fields = {'f1': Field(...), 'f2': Field(...)}
>>> fc.add_fields(fields)
>>> fc.fields
['OID@', 'SHAPE@', 'f1', 'f2']
Source code in src/arcpie/featureclass.py
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
def add_fields(self, fields: dict[str, Field]) -> None:
    """Provide a mapping of fieldnames to Fields

    Args:
        fields (dict[str, Field]): A mapping of fieldnames to Field objects

    Example:
        ```python
        >>> fields = {'f1': Field(...), 'f2': Field(...)}
        >>> fc.add_fields(fields)
        >>> fc.fields
        ['OID@', 'SHAPE@', 'f1', 'f2']
        ```
    """
    for fieldname, field in fields.items():
        self.add_field(fieldname, field)

add_to_map

add_to_map(
    map: Map,
    pos: Literal[
        "AUTO_ARRANGE", "BOTTOM", "TOP"
    ] = "AUTO_ARRANGE",
) -> None

Add the featureclass to a map

Note

If the Table or FeatureClass has a layer, the bound layer will be added to the map. Otherwise a default layer will be added. And the new layer will be bound to the Table or FeatureClass

PARAMETER DESCRIPTION

map

The map to add the featureclass to

TYPE: Map

Source code in src/arcpie/featureclass.py
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
def add_to_map(self, map: Map, pos: Literal['AUTO_ARRANGE', 'BOTTOM', 'TOP']='AUTO_ARRANGE') -> None:
    """Add the featureclass to a map

    Note: 
        If the Table or FeatureClass has a layer, the bound layer will be added to the map. 
        Otherwise a default layer will be added. And the new layer will be bound to the Table or FeatureClass

    Args:
        map (Map): The map to add the featureclass to
    """
    if not self.layer:
        # Create a default layer, bind it, remove, and add back
        # with addLayer to match behavior with existing bound layer
        self.layer = map.addDataFromPath(self.path) #type:ignore (Always Layer)
        map.removeLayer(self.layer) #type:ignore (Incorrect Signature)
    map.addLayer(self.layer, pos) #type:ignore

bind_to_layer

bind_to_layer(layer: Layer) -> None

Update the provided layer's datasource to this Table or FeatureClass

PARAMETER DESCRIPTION

layer

The layer to update connection properties for

TYPE: Layer

Raises: ValueError

Source code in src/arcpie/featureclass.py
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
def bind_to_layer(self, layer: Layer) -> None:
    """Update the provided layer's datasource to this Table or FeatureClass

    Args:
        layer (Layer): The layer to update connection properties for

    Raises: ValueError
    """
    # Use the wrapped Layer from arcpie.project so workspace can be inferred
    from .project import Layer as _Layer
    layer = _Layer(layer)
    # Try to update datasource using updateConnectionProperties
    try:
        layer.updateConnectionProperties(layer.feature_class.workspace, self.workspace)
        return
    except:
        pass

    # Fallback to direct CIM update (updateConnectionProperties is buggy)
    # TODO: Integrate cimple.cim here for typing
    try:
        definition = layer.cim
        dc = definition.featureTable.dataConnection # type: ignore
        dc.workspaceConnectionString = f'DATABASE={self.workspace}'
        dc.dataset = self.name
        # Remove missing FeatureDataset subpaths
        if dc.featureDataset and dc.featureDataset not in Path(self.path).parts: # type: ignore
            dc.featureDataset = None
        layer.setDefinition(definition) # type: ignore
    except Exception as e:
        raise ValueError(f'Unable to bind to layer: {e}')

clear

clear() -> None

Clear all records from the table

Source code in src/arcpie/featureclass.py
1031
1032
1033
1034
1035
def clear(self) -> None:
    """Clear all records from the table"""
    with self.update_cursor(self.oid_field_name) as cur:
        for _ in cur:
            cur.deleteRow()

copy

copy() -> FeatureClass[_GeometryType, _Schema]

Create a new FeatureClass instance to prevent overriding a shared resource

Source code in src/arcpie/featureclass.py
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
def copy(self) -> FeatureClass[_GeometryType, _Schema]:
    """Create a new FeatureClass instance to prevent overriding a shared resource"""
    return FeatureClass[_GeometryType, _Schema](
        self._path, 
        search_options=self.search_options.copy(),
        update_options=self.update_options.copy(),
        insert_options=self.insert_options.copy(),
        clause=self.clause,
        shape_token=self.shape_token,
    )

copy_to

copy_to(workspace: str, options: bool = True) -> Self

Copy this Table or FeatureClass to a new workspace

PARAMETER DESCRIPTION

workspace

The path to the workspace

TYPE: str

options

Copy the cursor options to the new Table or FeatureClass (default: True)

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
Table or FeatureClass

A Table or FeatureClass instance of the copied features

Example
>>> new_fc = fc.copy('workspace2')
>>> new_fc == fc
False
Source code in src/arcpie/featureclass.py
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
def copy_to(self, workspace: str, options: bool=True) -> Self:
    """Copy this `Table` or `FeatureClass` to a new workspace

    Args:
        workspace (str): The path to the workspace
        options (bool): Copy the cursor options to the new `Table` or `FeatureClass` (default: `True`)

    Returns:
        (Table or FeatureClass): A `Table` or `FeatureClass` instance of the copied features

    Example:
        ```python
        >>> new_fc = fc.copy('workspace2')
        >>> new_fc == fc
        False
        ```
    """
    #name = Path(self.path).relative_to(Path(self.workspace))
    if Exists(copy_fc := Path(workspace) / self.name):
        raise ValueError(f'{self.name} already exists in {workspace}!')
    CopyFeatures(self.path, str(copy_fc))
    fc = self.__class__(str(copy_fc))
    if options:
        fc.search_options = self.search_options
        fc.update_options = self.update_options
        fc.insert_options = self.insert_options
        fc.clause = self.clause
    return fc

create_annotations

create_annotations(
    name: str,
    reference_scale: float,
    *,
    single_class: bool = False,
    require_symbol: bool = False,
    auto_create: bool = True,
    auto_update: bool = True,
) -> FeatureClass

Create an AnnotationClass for the Features (requires a linked Layer object)

PARAMETER DESCRIPTION

name

The name of the annotation feature layer (can include a dataset, e.g. dataset/anno_50)

TYPE: str

reference_scale

The reference scale of the output annotation features

TYPE: float

single_class

Merge all annotations will be merged into one class

TYPE: bool DEFAULT: False

require_symbol

Symbol must be selected from the symbol collection

TYPE: bool DEFAULT: False

auto_create

Create a new annotation when a new feature is created

TYPE: bool DEFAULT: True

auto_update

Update the annotation when the linked feature is modified

TYPE: bool DEFAULT: True

Note

If the output Annotation Features already exist, the new annotations will be appended

Source code in src/arcpie/featureclass.py
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
def create_annotations(self, name: str, reference_scale: float, 
                       *,
                       single_class: bool = False,
                       require_symbol: bool = False,
                       auto_create: bool = True,
                       auto_update: bool = True,
    ) -> FeatureClass:
    """Create an AnnotationClass for the Features (requires a linked Layer object)

    Args:
        name: The name of the annotation feature layer (can include a dataset, e.g. `dataset/anno_50`)
        reference_scale: The reference scale of the output annotation features
        single_class: Merge all annotations will be merged into one class
        require_symbol: Symbol must be selected from the symbol collection
        auto_create: Create a new annotation when a new feature is created
        auto_update: Update the annotation when the linked feature is modified

    Note:
        If the output Annotation Features already exist, the new annotations will be appended
    """
    if self.layer is None:
        raise ValueError(
            f'Raw FeatureClasses cannot be used to create Annotations, '
            'requires a linked Layer'
        )
    return FeatureClass(
        AppendAnnotation(
            input_features=self.layer,
            output_featureclass=str(Path(self.path) / name),
            reference_scale=reference_scale,
            create_single_class='ONE_CLASS_ONLY' if single_class else 'CREATE_CLASSES',
            require_symbol_from_table='REQUIRE_SYMBOL' if require_symbol else 'NO_SYMBOL_REQUIRED',
            create_annotation_when_feature_added='AUTO_CREATE' if auto_create else 'NO_AUTO_CREATE',
            update_annotation_when_feature_modified='AUTO_UPDATE' if auto_update else 'NO_AUTO_UPDATE',
        )[0]
    )

delete

delete() -> None

Delete the object permanently using arcpy.management.Delete

Note: After calling this method, the current FeatureClass object becomes unbound so del is called on self

Source code in src/arcpie/featureclass.py
877
878
879
880
881
882
883
def delete(self) -> None:
    """Delete the object permanently using arcpy.management.Delete

    Note: After calling this method, the current FeatureClass object becomes unbound so `del` is called on `self`
    """
    Delete(self.path)
    del self

delete_field

delete_field(fieldname: str) -> None

Delete a field from a Table or FeatureClass

PARAMETER DESCRIPTION

fieldname

The name of the field to delete/drop

TYPE: str

Example
>>> print(fc.fields)
['OID@', 'SHAPE@', 'name', 'year', 'month']

>>> del fc['month']
>>> print(fc.fields)
['OID@', 'SHAPE@', 'name', 'year']
>>> fc.delete_field('year')
>>> print(fc.fields)
['OID@', 'SHAPE@', 'name']
Source code in src/arcpie/featureclass.py
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
def delete_field(self, fieldname: str) -> None:
    """Delete a field from a Table or FeatureClass

    Args:
        fieldname (str): The name of the field to delete/drop

    Example:
        ```python
        >>> print(fc.fields)
        ['OID@', 'SHAPE@', 'name', 'year', 'month']

        >>> del fc['month']
        >>> print(fc.fields)
        ['OID@', 'SHAPE@', 'name', 'year']
        >>> fc.delete_field('year')
        >>> print(fc.fields)
        ['OID@', 'SHAPE@', 'name']
        ```
    """
    if fieldname in self.Tokens:
        raise ValueError(f"{fieldname} is a Token and cannot be deleted!")
    if not self.has_field(fieldname):
        raise ValueError(f"{fieldname} does not exist in {self.name}")
    with EnvManager(workspace=self.workspace):
        DeleteField(self.path, fieldname)
        self._fields = None # Defer new field check to next access

delete_identical

delete_identical(
    field_names: Iterable[FieldName] | FieldName,
) -> dict[int, int]

Delete all records that have matching field values

PARAMETER DESCRIPTION

field_names

The fields used to define an identical feature

TYPE: Sequence[FieldName] | FieldName

RETURNS DESCRIPTION
dict[int, int]

A dictionary of count of identical features deleted per feature

Note

Insertion order takes precidence unless the Table or FeatureClass is ordered. The first feature found by the cursor will be maintained and all subsequent matches will be removed

Source code in src/arcpie/featureclass.py
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
def delete_identical(self, field_names: Iterable[FieldName] | FieldName) -> dict[int, int]:
    """Delete all records that have matching field values

    Args:
        field_names (Sequence[FieldName] | FieldName): The fields used to define an identical feature

    Returns:
        (dict[int, int]): A dictionary of count of identical features deleted per feature

    Note:
        Insertion order takes precidence unless the Table or FeatureClass is ordered. The first feature found
        by the cursor will be maintained and all subsequent matches will be removed
    """
    # All
    if isinstance(field_names, str):
        field_names = [field_names]

    unique: dict[int, tuple[Any]] = {}
    deleted: dict[int, int] = {}
    with self.update_cursor('OID@', *field_names) as cur:
        for row in cur:
            oid: int = row[0]
            row = tuple(row[1:])
            for match_id, match_row in unique.items():
                if all(a == b for a, b in zip(row, match_row)):
                    match = match_id
                    break
            else:
                match = False

            if not match:
                unique[oid] = row

            else:
                deleted.setdefault(match, 0)
                deleted[match] += 1
                cur.deleteRow()
    return deleted

delete_where

delete_where(clause: WhereClause | str) -> None

Delete all records that match the provided where clause

PARAMETER DESCRIPTION

clause

The SQL query that determines the records that will be deleted

TYPE: WhereClause | str

Source code in src/arcpie/featureclass.py
1037
1038
1039
1040
1041
1042
1043
1044
def delete_where(self, clause: WhereClause|str) -> None:
    """Delete all records that match the provided where clause

    Args:
        clause (WhereClause|str): The SQL query that determines the records that will be deleted
    """
    with self.where(clause):
        self.clear()

distinct

Yield rows of distinct values

PARAMETER DESCRIPTION

distinct_fields

The field or fields to find distinct values for. Choosing multiple fields will find all distinct instances of those field combinations

TYPE: FieldOpt

YIELDS DESCRIPTION
tuple[Any, ...]

A tuple containing the distinct values (single fields will yield (value, ) tuples)

Source code in src/arcpie/featureclass.py
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
def distinct(self, distinct_fields: Iterable[FieldName] | FieldName) -> Iterator[tuple[Any, ...]]:
    """Yield rows of distinct values

    Args:
        distinct_fields (FieldOpt): The field or fields to find distinct values for.
            Choosing multiple fields will find all distinct instances of those field combinations

    Yields:
        ( tuple[Any, ...] ): A tuple containing the distinct values (single fields will yield `(value, )` tuples)
    """
    clause = SQLClause(prefix=f'DISTINCT {format_query_list(distinct_fields)}', postfix=None)
    try:
        yield from (value for value in self.search_cursor(*distinct_fields, sql_clause=clause))
    except RuntimeError: # Fallback when DISTINCT is not available or fails with Token input
        yield from sorted(set(self.get_tuples(distinct_fields)))

exists

exists() -> bool

Check if the Table or FeatureClass actually exists (check for deletion or initialization with bad path)

Source code in src/arcpie/featureclass.py
914
915
916
def exists(self) -> bool:
    """Check if the Table or FeatureClass actually exists (check for deletion or initialization with bad path)"""
    return Exists(str(self))

fields_as

fields_as(*fields: FieldName)

Override the default fields for the Table or FeatureClass so all non-explicit Iterators will only yield these fields (e.g. for row in fc: ...)

PARAMETER DESCRIPTION

*fields

Varargs of the fieldnames to limit all unspecified Iterators to

TYPE: FieldName DEFAULT: ()

Example
>>> with fc.fields_as('OID@', 'NAME'):
...     for row in fc:
...         print(row)
{'OID@': 1, 'NAME': 'John'}
{'OID@': 2, 'NAME': 'Michael'}
...
>>> for row in fc:
...     print(row)
{'OID@': 1, 'NAME': 'John', 'AGE': 75, 'ADDRESS': 123 Silly Walk}
{'OID@': 2, 'NAME': 'Michael', 'AGE': 70, 'ADDRESS': 42 Dead Parrot Blvd}
...
Source code in src/arcpie/featureclass.py
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
@contextmanager
def fields_as(self, *fields: FieldName):
    """Override the default fields for the Table or FeatureClass so all non-explicit Iterators will
    only yield these fields (e.g. `for row in fc: ...`)

    Args:
        *fields (FieldName): Varargs of the fieldnames to limit all unspecified Iterators to

    Example:
        ```python
        >>> with fc.fields_as('OID@', 'NAME'):
        ...     for row in fc:
        ...         print(row)
        {'OID@': 1, 'NAME': 'John'}
        {'OID@': 2, 'NAME': 'Michael'}
        ...
        >>> for row in fc:
        ...     print(row)
        {'OID@': 1, 'NAME': 'John', 'AGE': 75, 'ADDRESS': 123 Silly Walk}
        {'OID@': 2, 'NAME': 'Michael', 'AGE': 70, 'ADDRESS': 42 Dead Parrot Blvd}
        ...
        ```
    """
    # Allow passing a single field as a string `fc.fields_as('OID@')` to maintain
    # The call format of *Cursor objects
    _fields = self.fields
    self._fields = tuple(fields)
    try:
        yield self
    finally:
        self._fields = _fields

filter

filter(
    func: FilterFunc[_Schema], invert: bool = False
) -> Iterator[_Schema]

Apply a function filter to rows in the Table or FeatureClass

PARAMETER DESCRIPTION

func

A callable that takes a row dictionary and returns True or False

TYPE: Callable[[dict[str, Any]], bool]

invert

Invert the function. Only yield rows that return False

TYPE: bool DEFAULT: False

YIELDS DESCRIPTION
dict[str, Any]

Rows in the Table or FeatureClass that match the filter (or inverted filter)

Example
>>> def area_filter(row: dict) -> bool:
>>>     return row['Area'] >= 10

>>> for row in fc:
>>>     print(row['Area'])
1
2
10
<etc>

>>> for row in fc.filter(area_filter):
>>>     print(row['Area'])
10
11
90
<etc>
Source code in src/arcpie/featureclass.py
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
def filter(self, func: FilterFunc[_Schema], invert: bool=False) -> Iterator[_Schema]:
    """Apply a function filter to rows in the Table or FeatureClass

    Args:
        func (Callable[[dict[str, Any]], bool]): A callable that takes a 
            row dictionary and returns True or False
        invert (bool): Invert the function. Only yield rows that return `False`

    Yields:
        ( dict[str, Any] ): Rows in the Table or FeatureClass that match the filter (or inverted filter)

    Example:
        ```python
        >>> def area_filter(row: dict) -> bool:
        >>>     return row['Area'] >= 10

        >>> for row in fc:
        >>>     print(row['Area'])
        1
        2
        10
        <etc>

        >>> for row in fc.filter(area_filter):
        >>>     print(row['Area'])
        10
        11
        90
        <etc>
        ```

    """
    if hasattr(func, 'fields'): # Allow decorated filters for faster iteration (see `filter_fields`)
        with self.fields_as(*getattr(func, 'fields')):
            yield from (row for row in self if func(row) == (not invert))
    else:
        yield from (row for row in self if func(row) == (not invert))

footprint

footprint(buffer: float, pairwise: bool) -> Polygon | None
footprint(buffer: float) -> Polygon | None
footprint(
    buffer: None, pairwise: bool
) -> _GeometryType | None
footprint(buffer: None) -> _GeometryType | None
footprint(*, pairwise: bool) -> _GeometryType | None
footprint() -> _GeometryType | None
footprint(
    buffer: float | None = None, pairwise: bool = True
) -> _GeometryType | Polygon | None

Merge all geometry in the featureclass using current SelectionOptions into a single geometry object to use as a spatial filter on other FeatureClasses

PARAMETER DESCRIPTION

buffer

Optional buffer (in feature units, respects projection context) to buffer by (default: None)

TYPE: float | None DEFAULT: None

pairwise

Will use the PairwiseBuffer and PairwiseDissolve functions to generate the footprint (default: True)

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
_GeometryType | Polygon | None

A merged Multi-Geometry of all feature geometries or None if no features in FeatureClass

If you have issues with footprint geometry, you can disable pairwise since that uses Pairwise functions.

when pairwise == False, an iterative geometry union is done with a buffer applied to the result (this can be exponentially slower)

Source code in src/arcpie/featureclass.py
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
def footprint(self, buffer: float | None = None, pairwise: bool = True) -> _GeometryType | Polygon | None:
    """Merge all geometry in the featureclass using current SelectionOptions into a single geometry object to use 
    as a spatial filter on other FeatureClasses

    Args:
        buffer: Optional buffer (in feature units, respects projection context) to buffer by (default: None)
        pairwise: Will use the PairwiseBuffer and PairwiseDissolve functions to generate the footprint (default: `True`)

    Returns:
        A merged Multi-Geometry of all feature geometries or `None` if no features in FeatureClass

    Note: If you have issues with footprint geometry, you can disable `pairwise` since that uses Pairwise functions.
        when `pairwise == False`, an iterative geometry union is done with a buffer applied to the result (this can be exponentially slower)
    """
    _feats = list(self.shapes)
    _count = len(_feats)
    if _count == 0:
        return None

    # Only use pairwise if the feature count is moderately large

    # NOTE: The cutoff for pairwise being faster depends on shape complexity
    # ~200 to 250 seems to be the sweetspot though. Direct merge is linear 
    # while pairwise is logarithmic with a base time of ~100ms while Geometry.merge
    # is ~0.1ms per feature, but increases as the feature gains points
    if pairwise and _count > 230:
        return self._footprint_pairwise(_feats, buffer)

    def merge(acc: _GeometryType | Polygon, nxt: _GeometryType | Polygon) -> _GeometryType | Polygon:
        return acc.union(nxt) # pyright: ignore[reportReturnType]

    # Consume the shape generator popping off the first shape and applying the buffer, 
    # Then buffering each additional shape and merging it into the accumulator (starting with _first)
    footprint = reduce(merge, _feats)
    if buffer:
        footprint = footprint.buffer(buffer)
    return footprint

from_layer classmethod

from_layer(
    layer: Layer,
    *,
    ignore_selection: bool = False,
    ignore_def_query: bool = False,
) -> FeatureClass[Any, Any]

Build a FeatureClass object from a layer applying the layer's current selection to the stored cursors

PARAMETER DESCRIPTION

layer

The layer to convert to a FeatureClass

TYPE: Layer

ignore_selection

Ignore the layer selection (default: False)

TYPE: bool DEFAULT: False

ignore_def_query

Ignore the layer definition query (default: False)

TYPE: bool DEFAULT: False

Returns: ( FeatureClass ): The FeatureClass object with the layer query applied

Source code in src/arcpie/featureclass.py
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
@classmethod
def from_layer(cls, layer: Layer,
               *,
               ignore_selection: bool = False,
               ignore_def_query: bool = False,) -> FeatureClass[Any, Any]:
    """Build a FeatureClass object from a layer applying the layer's current selection to the stored cursors

    Args:
        layer (Layer): The layer to convert to a FeatureClass
        ignore_selection (bool): Ignore the layer selection (default: False)
        ignore_def_query (bool): Ignore the layer definition query (default: False)
    Returns:
        ( FeatureClass ): The FeatureClass object with the layer query applied
    """
    fc = cls(layer.dataSource)

    selected_ids: set[int] | None = (
        layer.getSelectionSet() or None
        if not ignore_selection 
        else None
    )
    definition_query: str|None = (
        layer.definitionQuery or None
        if not ignore_def_query 
        else None
    )
    selection: str|None = (
        f"{fc.oid_field_name} IN ({format_query_list(selected_ids)})" 
        if selected_ids 
        else None
    )

    if (query_components := list(filter(None, [definition_query, selection]))):
        where_clause = ' AND '.join(query_components)
        fc.search_options = SearchOptions(where_clause=where_clause)
        fc.update_options = UpdateOptions(where_clause=where_clause)

    fc.layer = layer
    return fc

from_table classmethod

from_table(
    table: Table,
    *,
    ignore_selection: bool = False,
    ignore_def_query: bool = False,
) -> Table

See from_layer for documentation, this is an alternative constructor that builds from a mp.Table object

Source code in src/arcpie/featureclass.py
1567
1568
1569
1570
1571
1572
1573
@classmethod
def from_table(cls, table: TableLayer,
               *,
               ignore_selection: bool = False,
               ignore_def_query: bool = False,) -> Table:
    """See `from_layer` for documentation, this is an alternative constructor that builds from a mp.Table object"""
    return Table.from_layer(table, ignore_selection=ignore_selection, ignore_def_query=ignore_def_query) # type: ignore (this won't break the interface)

get

get(
    field: tuple[FieldName, ...], default: _T
) -> Iterator[tuple[Any, ...]] | _T
get(
    field: list[FieldName], default: _T
) -> Iterator[list[Any]] | _T
get(
    field: set[FieldName], default: _T
) -> Iterator[_Schema] | _T
get(
    field: Literal["SHAPE@"], default: _T
) -> Iterator[_GeometryType] | _T
get(field: FieldName, default: _T) -> Iterator[Any] | _T
get(
    field: FilterFunc[_Schema], default: _T
) -> Iterator[_Schema] | _T
get(
    field: WhereClause, default: _T
) -> Iterator[_Schema] | _T
get(field: None, default: _T) -> Iterator[None] | _T
get(
    field: GeometryType | Extent, default: _T
) -> Iterator[_Schema] | _T
get(
    field: _IndexableTypes
    | FilterFunc[_Schema]
    | Extent
    | GeometryType
    | Literal["SHAPE@"],
    default: _T = None,
) -> Iterator[Any] | _T

Allows safe indexing of a FeatureClass, see Table.get for more information

Source code in src/arcpie/featureclass.py
1965
1966
1967
1968
1969
1970
1971
1972
def get(self, field: Table._IndexableTypes | FilterFunc[_Schema] | Extent | GeometryType | Literal['SHAPE@'], default: _T=None) -> Iterator[Any] | _T: # pyright: ignore[reportIncompatibleMethodOverride]
    """Allows safe indexing of a FeatureClass, see `Table.get` for more information"""
    try:
        return self[field]
    except (KeyError, RuntimeError) as e:
        if isinstance(e, RuntimeError) and 'Cannot find field' in str(e):
            raise
        return default

get_records

Generate row dicts with in the form {field: value, ...} for each row in the cursor

PARAMETER DESCRIPTION

field_names

The columns to iterate

TYPE: str | Iterable[str]

**options

Additional options to pass on to the cursor

TYPE: Unpack[SearchOptions] DEFAULT: {}

Yields ( dict[str, Any] ): A mapping of fieldnames to field values for each row

Source code in src/arcpie/featureclass.py
738
739
740
741
742
743
744
745
746
747
748
def get_records(self, field_names: Iterable[FieldName] | FieldName, **options: Unpack[SearchOptions]) -> Iterator[_Schema]:
    """Generate row dicts with in the form `{field: value, ...}` for each row in the cursor

    Args:
        field_names (str | Iterable[str]): The columns to iterate
        **options (Unpack[SearchOptions]): Additional options to pass on to the cursor
    Yields 
        ( dict[str, Any] ): A mapping of fieldnames to field values for each row
    """
    with self.search_cursor(*field_names, **options) as cur:
        yield from self.as_dict(cur)

get_schema

get_schema(
    *,
    fallback_type: type = object,
    docs: dict[str, str] | None = None,
    include_shape_token: bool = True,
    include_oid_token: bool = True,
    default_doc: Callable[[Field], str]
    | None
    | Literal["nodoc"] = None,
) -> str

Get python code for the Table/FeatureClass schema

    Args:
        fallback_type: The default type annotation for any fields that aren't mapped properly
        docs: Optional docs to include for each field (e.g. `{'FieldName': 'field doc', ...}`)
        include_shape_token: Include a `SHAPE@` key with the FeatureClass shape type (no effect on Tables)
        include_oid_token: Include the `OID@` key
        default_doc: A function that takes a Field dictionary and retuens a formatted doc (default: `k: v

...`)

Source code in src/arcpie/featureclass.py
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
def get_schema(self,
               *,
               fallback_type: type = object,
               docs: dict[str, str] | None = None,
               include_shape_token: bool = True,
               include_oid_token: bool = True,
               default_doc: Callable[[Field], str] | None | Literal['nodoc'] = None
) -> str:
    """Get python code for the Table/FeatureClass schema

    Args:
        fallback_type: The default type annotation for any fields that aren't mapped properly
        docs: Optional docs to include for each field (e.g. `{'FieldName': 'field doc', ...}`)
        include_shape_token: Include a `SHAPE@` key with the FeatureClass shape type (no effect on Tables)
        include_oid_token: Include the `OID@` key
        default_doc: A function that takes a Field dictionary and retuens a formatted doc (default: `k: v\n\n...`)
    """
    # Deferred Import since yield_schema does an instance check on FeatureClass/Table
    from arcpie.schema.field import yield_schema
    if default_doc is None:
        return '\n'.join(
            yield_schema(
                self, 
                fallback_type=fallback_type, 
                docs=docs, 
                include_oid_token=include_oid_token, 
                include_shape_token=include_shape_token
            )
        )
    else:
        return '\n'.join(
            yield_schema(
                self, 
                fallback_type=fallback_type, 
                docs=docs, 
                include_oid_token=include_oid_token, 
                include_shape_token=include_shape_token,
                # Only override the default formatter if None is given
                # if Literal 'nodoc' is supplied, use closure with empty string as doc func
                default_doc=(lambda f: '') if default_doc == 'nodoc' else default_doc
            )
        )

get_transformation

get_transformation(to_ref: SpatialReference) -> str | None

Get the name of the transformation to convert from feature reference to provided reference

PARAMETER DESCRIPTION

to_ref

The spatial reference to get a transformation for

TYPE: SpatialReference

RETURNS DESCRIPTION
str | None

The name of the first transformation or None if no transformation available

Source code in src/arcpie/featureclass.py
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
def get_transformation(self, to_ref: SpatialReference) -> str | None:
    """Get the name of the transformation to convert from feature reference to provided reference

    Args:
        to_ref (SpatialReference): The spatial reference to get a transformation for

    Returns:
        (str | None): The name of the first transformation or None if no transformation available
    """
    trans = ListTransformations(self.spatial_reference, to_ref, first_only=True)
    if not trans:
        return None
    return trans[0]

get_tuples

Generate tuple rows in the for (val1, val2, ...) for each row in the cursor

PARAMETER DESCRIPTION

field_names

The columns to iterate

TYPE: str | Iterable[str]

**options

Additional parameters to pass to the SearchCursor

TYPE: SearchOptions DEFAULT: {}

Source code in src/arcpie/featureclass.py
750
751
752
753
754
755
756
757
758
def get_tuples(self, field_names: Iterable[FieldName] | FieldName, **options: Unpack[SearchOptions]) -> Iterator[tuple[Any, ...]]:
    """Generate tuple rows in the for (val1, val2, ...) for each row in the cursor

    Args:
        field_names (str | Iterable[str]): The columns to iterate
        **options (SearchOptions): Additional parameters to pass to the SearchCursor
    """
    with self.search_cursor(*field_names, **options) as cur:
        yield from cur

group_by

group_by(
    group_fields: Sequence[FieldName] | FieldName,
    return_fields: Sequence[FieldName] | FieldName = "*",
) -> Iterator[tuple[GroupIdent, GroupIter]]

Group features by matching field values and yield full records in groups

PARAMETER DESCRIPTION

group_fields

The fields to group the data by

TYPE: FieldOpt

return_fields

The fields to include in the output record ('*' means all and is default)

TYPE: FieldOpt DEFAULT: '*'

Yields: ( Iterator[tuple[tuple[FieldName, ...], Iterator[tuple[Any, ...] | Any]]] ): A nested iterator of groups and then rows

Example
>>> # With a field group, you will be able to unpack the tuple
>>> for group, rows in fc.group_by(['GroupField1', 'GroupField2'], ['ValueField1', 'ValueField2', ...]):
...     print(group)
...     for v1, v2 in rows:
...        if v1 > 10:
...            print(v2)
(GroupValue1A, GroupValue1B)
valueA
valueB
...
>>> # With a single field, you will have direct access to the field values   
>>> for group, district_populations in fc.group_by(['City', 'State'], 'Population'):
>>>         print(f"{group}: {sum(district_populations)}")
(New York, NY): 8260000
(Boston, MA): 4941632
...
Source code in src/arcpie/featureclass.py
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
def group_by(self, group_fields: Sequence[FieldName] | FieldName, return_fields: Sequence[FieldName] | FieldName ='*') -> Iterator[tuple[GroupIdent, GroupIter]]:
    """Group features by matching field values and yield full records in groups

    Args:
        group_fields (FieldOpt): The fields to group the data by
        return_fields (FieldOpt): The fields to include in the output record (`'*'` means all and is default)
    Yields:
        ( Iterator[tuple[tuple[FieldName, ...], Iterator[tuple[Any, ...] | Any]]] ): A nested iterator of groups and then rows

    Example:
        ```python
        >>> # With a field group, you will be able to unpack the tuple
        >>> for group, rows in fc.group_by(['GroupField1', 'GroupField2'], ['ValueField1', 'ValueField2', ...]):
        ...     print(group)
        ...     for v1, v2 in rows:
        ...        if v1 > 10:
        ...            print(v2)
        (GroupValue1A, GroupValue1B)
        valueA
        valueB
        ...
        >>> # With a single field, you will have direct access to the field values   
        >>> for group, district_populations in fc.group_by(['City', 'State'], 'Population'):
        >>>         print(f"{group}: {sum(district_populations)}")
        (New York, NY): 8260000
        (Boston, MA): 4941632
        ...
        ```
    """

    # Parameter Validations
    if isinstance(group_fields, str):
        group_fields = (group_fields,)
    if return_fields == '*':
        return_fields = self.fields
    if isinstance(return_fields, str):
        return_fields = (return_fields,)
    if len(group_fields) < 1 or len(return_fields) < 1:
        raise ValueError("Group Fields and Return Fields must be populated")

    group_fields = list(group_fields)
    return_fields = list(return_fields)
    _all_fields = group_fields + return_fields
    for group in self.distinct(group_fields):
        group_key = {field : value for field, value in zip(group_fields, group)}
        where_clause = " AND ".join(f"{field} = {norm(value)}" for field, value in group_key.items())
        if '@' not in where_clause: # Handle valid clause (no tokens)
            with self.search_cursor(*return_fields, where_clause=where_clause) as group_cur:
                yield (extract_singleton(group), (extract_singleton(row) for row in group_cur))
        else: # Handle token being passed by iterating a cursor and checking values directly
            for row in filter(lambda row: all(row[k] == group_key[k] for k in group_key), self[set(_all_fields)]):
                yield (extract_singleton(group), (row.pop(k) for k in return_fields)) # type: ignore (TypedDict Generic causes issues)

has_field

has_field(fieldname: str) -> bool

Check if the field exists in the featureclass or is a valid Token (@[TOKEN])

Source code in src/arcpie/featureclass.py
925
926
927
def has_field(self, fieldname: str) -> bool:
    """Check if the field exists in the featureclass or is a valid Token (@[TOKEN])"""
    return fieldname in self.fields or fieldname in self.Tokens

insert_cursor

insert_cursor(
    *field_names: FieldName,
    insert_options: InsertOptions | None = None,
    **overrides: Unpack[InsertOptions],
) -> InsertCursor

See Table.search_cursor doc for general info. Operation of this method is identical but returns an InsertCursor

Source code in src/arcpie/featureclass.py
586
587
588
589
590
591
592
593
594
def insert_cursor(self, *field_names: FieldName,
                  insert_options: InsertOptions|None=None, 
                  **overrides: Unpack[InsertOptions]) -> InsertCursor:
    """See `Table.search_cursor` doc for general info. Operation of this method is identical but returns an `InsertCursor`"""
    if 'datum_transformation' in overrides and overrides['datum_transformation'] is None:
        overrides.pop('datum_transformation')
    if insert_options and 'datum_transformation' in insert_options and insert_options['datum_transformation'] is None:
        insert_options.pop('datum_transformation')
    return InsertCursor(self.path, field_names, **self._resolve_insert_options(insert_options, overrides))

insert_record

insert_record(
    record: _Schema, ignore_errors: bool = False
) -> int | None

Insert a single record into the table

Source code in src/arcpie/featureclass.py
760
761
762
763
764
765
766
767
768
def insert_record(self, record: _Schema, ignore_errors: bool=False) -> int | None:
    """Insert a single record into the table"""
    if missing_fields := set(record.keys()).difference(self.fields):
        if ignore_errors:
            return None
        else:
            raise ValueError(f'{missing_fields} not in {self.fields}')
    with self.insert_cursor(*record.keys()) as cur:
        return cur.insertRow(list(record.values()))

insert_records

insert_records(
    records: Iterable[_Schema], ignore_errors: bool = False
) -> Iterator[int]

Provide an iterable of records to insert Args: records (Iterable[RowRecord]): The sequence of records to insert ignore_errors (bool): Ignore per-row errors and continue. Otherwise raise KeyError (default: True)

RETURNS DESCRIPTION
Iterator[int]

Returns the OIDs of the newly inserted rows

RAISES DESCRIPTION
KeyError

If the records have varying keys or the keys are not in the Table or FeatureClass

Example
>>> new_rows = [
...    {'first': 'John', 'last': 'Cleese', 'year': 1939}, 
...    {'first': 'Michael', 'last': 'Palin', 'year': 1943}
... ]
>>> print(fc.insert_rows(new_rows))
(2,3)

>>> # Insert all shapes from fc into fc2
>>> fc2.insert_rows(fc.get_records(['first', 'last', 'year']))
(1,2)
Source code in src/arcpie/featureclass.py
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
def insert_records(self, records: Iterable[_Schema] , ignore_errors: bool=False) -> Iterator[int]:
    """Provide an iterable of records to insert
    Args:
        records (Iterable[RowRecord]): The sequence of records to insert
        ignore_errors (bool): Ignore per-row errors and continue. Otherwise raise KeyError (default: True)

    Returns:
        ( Iterator[int] ): Returns the OIDs of the newly inserted rows

    Raises:
        ( KeyError ): If the records have varying keys or the keys are not in the Table or FeatureClass

    Example:
        ```python
        >>> new_rows = [
        ...    {'first': 'John', 'last': 'Cleese', 'year': 1939}, 
        ...    {'first': 'Michael', 'last': 'Palin', 'year': 1943}
        ... ]
        >>> print(fc.insert_rows(new_rows))
        (2,3)

        >>> # Insert all shapes from fc into fc2
        >>> fc2.insert_rows(fc.get_records(['first', 'last', 'year']))
        (1,2)
        ```
    """
    yield from filter(None, (self.insert_record(record, ignore_errors=ignore_errors) for record in records))

is_empty

is_empty() -> bool

Check if a Table/FeatureClass is empty

Source code in src/arcpie/featureclass.py
918
919
920
921
922
923
def is_empty(self) -> bool:
    """Check if a Table/FeatureClass is empty"""
    for _ in self:
        return False
    else:
        return True

options

options(
    *,
    strict: bool = False,
    search_options: SearchOptions | None = None,
    update_options: UpdateOptions | None = None,
    insert_options: InsertOptions | None = None,
    clause: SQLClause | None = None,
)

Enter a context block where the supplied options replace the stored options for the Table or FeatureClass

PARAMETER DESCRIPTION

strict

If this is set to True the Table or FeatureClass will not fallback on existing options when set to False, provided options override existing options (default: False)

TYPE: bool DEFAULT: False

search_options

Contextual search overrides

TYPE: SearchOptions DEFAULT: None

update_options

Contextual update overrides

TYPE: UpdateOptions DEFAULT: None

insert_options

Contextual insert overrides

TYPE: InsertOptions DEFAULT: None

clause

Contextual sql_clause override

TYPE: SQLClause DEFAULT: None

Source code in src/arcpie/featureclass.py
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
@contextmanager
def options(self,
            *, 
            strict: bool = False,
            search_options: SearchOptions|None=None, 
            update_options: UpdateOptions|None=None, 
            insert_options: InsertOptions|None=None, 
            clause: SQLClause|None=None):
    """Enter a context block where the supplied options replace the stored options for the `Table` or `FeatureClass`

    Args:
        strict (bool): If this is set to `True` the `Table` or `FeatureClass` will not fallback on existing options
            when set to `False`, provided options override existing options (default: `False`)
        search_options (SearchOptions): Contextual search overrides
        update_options (UpdateOptions): Contextual update overrides
        insert_options (InsertOptions): Contextual insert overrides
        clause (SQLClause): Contextual `sql_clause` override
    """
    _src_ops = self.search_options
    _upd_ops = self.update_options
    _ins_ops = self.insert_options
    _clause  = self.clause
    try:
        self._search_options = (
            self._resolve_search_options(_src_ops, search_options or {}) 
            if not strict
            else search_options or SearchOptions()
        )
        self._update_options = (
            self._resolve_update_options(_upd_ops, update_options or {})
            if not strict 
            else insert_options or UpdateOptions()
        )
        self._insert_options = (
            self._resolve_insert_options(_ins_ops, insert_options or {})
            if not strict 
            else insert_options or InsertOptions()
        )
        self._clause = (
            clause or _clause
            if not strict 
            else SQLClause(None, None)
        )
        yield self

    finally:
        self._search_options = _src_ops
        self._update_options = _upd_ops
        self.insert_options = _ins_ops
        self._clause = _clause

recalculate_extent

recalculate_extent() -> None

Recalculate the FeatureClass Extent

Source code in src/arcpie/featureclass.py
1863
1864
1865
def recalculate_extent(self) -> None:
    """Recalculate the FeatureClass Extent"""
    RecalculateFeatureClassExtent(self.path, 'STORE_EXTENT')

reference_as

reference_as(spatial_reference: SpatialReference)

Allows you to temporarily set a spatial reference on SearchCursor and UpdateCursor objects within a context block

PARAMETER DESCRIPTION

spatial_reference

The spatial reference to apply to the cursor objects

TYPE: SpatialReference

YIELDS DESCRIPTION
self

Mutated self with search and update options set to use the provided spatial reference

Example
>>> sr = arcpy.SpatialReference(26971)
>>> fc = FeatureClass[Polygon]('<fc_path>')

>>> orig_shapes = list(fc.shapes)

>>> with fc.project_as(sr):
...     proj_shapes = list(fc.shapes)

>>> print(orig_shapes[0].spatialReference)
SpatialReference(4326)

>>> print(proj_shapes[0].spatialReference)
SpatialReference(26971)
Source code in src/arcpie/featureclass.py
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
@contextmanager
def reference_as(self, spatial_reference: SpatialReference):
    """Allows you to temporarily set a spatial reference on SearchCursor and UpdateCursor objects within a context block

    Args:
        spatial_reference (SpatialReference): The spatial reference to apply to the cursor objects

    Yields:
        (self): Mutated self with search and update options set to use the provided spatial reference

    Example:
        ```python
        >>> sr = arcpy.SpatialReference(26971)
        >>> fc = FeatureClass[Polygon]('<fc_path>')

        >>> orig_shapes = list(fc.shapes)

        >>> with fc.project_as(sr):
        ...     proj_shapes = list(fc.shapes)

        >>> print(orig_shapes[0].spatialReference)
        SpatialReference(4326)

        >>> print(proj_shapes[0].spatialReference)
        SpatialReference(26971)
        ```
    """
    with self.options(
        search_options=SearchOptions(spatial_reference=spatial_reference), 
        update_options=UpdateOptions(spatial_reference=spatial_reference)):
        yield self

row_updater

row_updater(
    *field_names: FieldName,
    strict: bool = False,
    update_options: UpdateOptions | None = None,
    **overrides: Unpack[UpdateOptions],
) -> Generator[_Schema, _Schema | None, None]

A Bi-Directional generator that yields rows and updates them with the sent value

Note

This method will assume the full provided schema if there is one, so make sure you keep track of any applied field filters.

PARAMETER DESCRIPTION

field_names

The fields to include in the update operation (default: All)

TYPE: FieldName | str DEFAULT: ()

strict

Raise a KeyError if an invalid fieldname is passed, otherwise drop invalid updates (default: False)

TYPE: bool DEFAULT: False

update_options

Additional context to pass to the UpdateCursor as a dictionary

TYPE: UpdateOptions DEFAULT: None

**overrides

Additional context to pass to the UpdateCursor as keyword arguments

TYPE: UpdateOptions DEFAULT: {}

Example
>>> updater = fc.row_updater()
>>> for row in updater:
...     if row['Name'] = 'No Name':
...         row['Name'] = None
...         updater.send(row)
Source code in src/arcpie/featureclass.py
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
def row_updater(self, *field_names: FieldName,
                strict: bool=False,
                update_options: UpdateOptions|None=None, 
                **overrides: Unpack[UpdateOptions]) -> Generator[_Schema, _Schema|None, None]:
    """A Bi-Directional generator that yields rows and updates them with the sent value

    Note:
        This method will assume the full provided schema if there is one, so make sure you keep track of
        any applied field filters.

    Args:
        field_names (FieldName|str): The fields to include in the update operation (default: All)
        strict (bool): Raise a KeyError if an invalid fieldname is passed, otherwise drop invalid updates (default: False)
        update_options (UpdateOptions): Additional context to pass to the UpdateCursor as a dictionary
        **overrides (UpdateOptions): Additional context to pass to the UpdateCursor as keyword arguments

    Example:
        ```python
        >>> updater = fc.row_updater()
        >>> for row in updater:
        ...     if row['Name'] = 'No Name':
        ...         row['Name'] = None
        ...         updater.send(row)
        ```
    """
    with self.update_cursor(*(field_names or self.fields), update_options=update_options, **overrides) as cur:
        for row in self.as_dict(cur):
            upd = yield row
            if strict and (invalid := set(upd or []) - set(row)):
                raise KeyError(f'{invalid} fields not found in {self.name}')
            if upd is not None:
                cur.updateRow([upd.get(f, row[f]) for f in cur.fields])

search_cursor

search_cursor(
    *field_names: FieldName,
    search_options: SearchOptions | None = None,
    **overrides: Unpack[SearchOptions],
) -> SearchCursor

Get a SearchCursor for the Table or FeatureClass Supplied search options are resolved by updating the base Table or FeatureClass Search options in this order:

**overrides['kwarg'] -> search_options['kwarg'] -> self.search_options['kwarg']

This is implemented using unpacking operations with the lowest importance option set being unpacked first

{**self.search_options, **(search_options or {}), **overrides}

With direct key word arguments (**overrides) shadowing all other supplied options. This allows a FeatureClass to be initialized using a base set of options, then a shared SearchOptions set to be applied in some contexts, then a direct keyword override to be supplied while never mutating the base options of the FeatureClass.

PARAMETER DESCRIPTION

field_names

The column names to include from the Table or FeatureClass

TYPE: str | Iterable[str] DEFAULT: ()

search_options

A SeachOptions instance that will be used to shadow search_options set on the Table or FeatureClass

TYPE: SearchOptions | None DEFAULT: None

**overrides

Additional keyword arguments for the cursor that shadow both the seach_options variable and the Table or FeatureClass instance SearchOptions

TYPE: Unpack[SeachOptions] DEFAULT: {}

RETURNS DESCRIPTION
SearchCursor

A SearchCursor for the Table or FeatureClass instance that has all supplied options resolved and applied

Example
    >>> cleese_search = SearchOptions(where_clause="NAME = 'John Cleese'")
    >>> idle_search = SearchOptions(where_clause="NAME = 'Eric Idle'")
    >>> monty = Table or FeatureClass('<path>', search_options=cleese_search)
    >>> print(list(monty.search_cursor('NAME')))
    [('John Cleese',)]
    >>> print(list(monty.search_cursor('NAME', search_options=idle_search)))
    [('Eric Idle', )]
    >>> print(list(monty.search_cursor('NAME', search_options=idle_search)), where_clause="NAME = Graham Chapman")
    [('Graham Chapman', )]

In this example, you can see that the keyword override is the most important. The fact that the other searches are created outside initialization allows you to store common queries in one place and update them for all cursors using them at the same time, while still allowing specific instances of a cursor to override those shared/stored defaults.

Source code in src/arcpie/featureclass.py
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
def search_cursor(self, *field_names: FieldName,
                  search_options: SearchOptions|None=None, 
                  **overrides: Unpack[SearchOptions]) -> SearchCursor:
    """Get a `SearchCursor` for the `Table` or `FeatureClass`
    Supplied search options are resolved by updating the base `Table` or `FeatureClass` Search options in this order:

    `**overrides['kwarg'] -> search_options['kwarg'] -> self.search_options['kwarg']`

    This is implemented using unpacking operations with the lowest importance option set being unpacked first

    `{**self.search_options, **(search_options or {}), **overrides}`

    With direct key word arguments (`**overrides`) shadowing all other supplied options. This allows a FeatureClass to
    be initialized using a base set of options, then a shared SearchOptions set to be applied in some contexts,
    then a direct keyword override to be supplied while never mutating the base options of the FeatureClass.

    Args:
        field_names (str | Iterable[str]): The column names to include from the `Table` or `FeatureClass`
        search_options (SearchOptions|None): A `SeachOptions` instance that will be used to shadow
            `search_options` set on the `Table` or `FeatureClass`
        **overrides ( Unpack[SeachOptions] ): Additional keyword arguments for the cursor that shadow 
            both the `seach_options` variable and the `Table` or `FeatureClass` instance `SearchOptions`

    Returns:
        ( SearchCursor ): A `SearchCursor` for the `Table` or `FeatureClass` instance that has all supplied options
            resolved and applied

    Example:
        ```python
            >>> cleese_search = SearchOptions(where_clause="NAME = 'John Cleese'")
            >>> idle_search = SearchOptions(where_clause="NAME = 'Eric Idle'")
            >>> monty = Table or FeatureClass('<path>', search_options=cleese_search)
            >>> print(list(monty.search_cursor('NAME')))
            [('John Cleese',)]
            >>> print(list(monty.search_cursor('NAME', search_options=idle_search)))
            [('Eric Idle', )]
            >>> print(list(monty.search_cursor('NAME', search_options=idle_search)), where_clause="NAME = Graham Chapman")
            [('Graham Chapman', )]
        ```
    In this example, you can see that the keyword override is the most important. The fact that the other searches are
    created outside initialization allows you to store common queries in one place and update them for all cursors using 
    them at the same time, while still allowing specific instances of a cursor to override those shared/stored defaults.
    """
    return SearchCursor(self.path, field_names, **self._resolve_search_options(search_options, overrides))

select

select(
    method: Literal[
        "NEW",
        "DIFFERENCE",
        "INTERSECT",
        "SYMDIFFERENCE",
        "UNION",
    ] = "NEW",
) -> None

If the Table or FeatureClass is bound to a layer, update the layer selection with the active SearchOptions

PARAMETER DESCRIPTION

method

The method to use to apply the selection

DIFFERENCE: Selects the features that are not in the current selection but are in the Table or FeatureClass.

INTERSECT: Selects the features that are in the current selection and the Table or FeatureClass.

NEW: Creates a new feature selection from the Table or FeatureClass.

SYMDIFFERENCE: Selects the features that are in the current selection or the Table or FeatureClass but not both.

UNION: Selects all the features in both the current selection and those in Table or FeatureClass.

TYPE: Literal['NEW', 'DIFFERENCE', 'INTERSECT', 'SYMDIFFERENCE', 'UNION'] DEFAULT: 'NEW'

Note

Selection changes require the project file to be saved to take effect.

Source code in src/arcpie/featureclass.py
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
def select(self, method: Literal['NEW','DIFFERENCE','INTERSECT','SYMDIFFERENCE','UNION']='NEW') -> None:
    """If the Table or FeatureClass is bound to a layer, update the layer selection with the active SearchOptions

    Args:
        method: The method to use to apply the selection\n
            `DIFFERENCE`: Selects the features that are not in the current selection but are in the Table or FeatureClass.\n
            `INTERSECT`: Selects the features that are in the current selection and the Table or FeatureClass.\n
            `NEW`: Creates a new feature selection from the Table or FeatureClass.\n
            `SYMDIFFERENCE`: Selects the features that are in the current selection or the Table or FeatureClass but not both.\n
            `UNION`: Selects all the features in both the current selection and those in Table or FeatureClass.\n

    Note:
        Selection changes require the project file to be saved to take effect. 
    """
    if self.layer:
        _selected = list(self['OID@'])
        self.layer.setSelectionSet(_selected, method=method)
        selected = self.layer.getSelectionSet() or set()
        _query = 'NO QUERY'
        try: # Try to select the layer in the active map
            if len(selected) == 1:
                _query = f'{self.oid_field_name} = {list(selected)[0]}'
            elif len(selected) > 1:
                _query = f'{self.oid_field_name} IN ({format_query_list(selected)})'
            else:
                return
            SelectLayerByAttribute(self.layer.longName, 'NEW_SELECTION', _query)
            return
        except Exception:
            return

spatial_filter

spatial_filter(
    spatial_filter: GeometryType | Extent,
    spatial_relationship: SpatialRelationship = "INTERSECTS",
)

Apply a spatial filter to the FeatureClass in a context

PARAMETER DESCRIPTION

spatial_filter

The geometry to use as a spatial filter

TYPE: Geometry | Extent

spatial_relationship

The relationship to check for (default: INTERSECTS)

TYPE: SpatialRelationship DEFAULT: 'INTERSECTS'

Example
>>> with fc.spatial_filter(boundary) as f:
...     print(len(fc))
100
>>> print(len(fc))
50000
Note

Same as with where, this method will be much faster than any manual filter you can apply using python. If you need to filter a FeatureClass by a spatial relationship, use this method, then do your expensive filter operation on the reduced dataset

>>> def expensive_filter(rec):
>>>     ...
>>> with fc.spatial_filter(boundary) as local:
>>>     for row in fc.filter(expensive_filter):
>>>         ...
Source code in src/arcpie/featureclass.py
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
@contextmanager
def spatial_filter(self, spatial_filter: GeometryType | Extent, spatial_relationship: SpatialRelationship='INTERSECTS'):
    """Apply a spatial filter to the FeatureClass in a context

    Args:
        spatial_filter (Geometry | Extent): The geometry to use as a spatial filter
        spatial_relationship (SpatialRelationship): The relationship to check for (default: `INTERSECTS`)

    Example:
        ```python
        >>> with fc.spatial_filter(boundary) as f:
        ...     print(len(fc))
        100
        >>> print(len(fc))
        50000
        ```

    Note:
        Same as with `where`, this method will be much faster than any manual `filter` you can apply using python. 
        If you need to filter a FeatureClass by a spatial relationship, use this method, then do your expensive 
        `filter` operation on the reduced dataset

        ```python
        >>> def expensive_filter(rec):
        >>>     ...
        >>> with fc.spatial_filter(boundary) as local:
        >>>     for row in fc.filter(expensive_filter):
        >>>         ...
        ```
    """
    with self.options(
        search_options=SearchOptions(
            spatial_filter=spatial_filter, 
            spatial_relationship=spatial_relationship)):
        yield self

unselect

unselect() -> None

If the Table or FeatureClass is bound to a layer, Remove layer selection

Note

Selection changes require the project file to be saved to take effect.

Source code in src/arcpie/featureclass.py
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
def unselect(self) -> None:
    """If the Table or FeatureClass is bound to a layer, Remove layer selection

    Note:
        Selection changes require the project file to be saved to take effect.
    """
    if self.layer:
        self.layer.setSelectionSet(method='NEW')
        try: # Try to unselect the layer in the active map
            SelectLayerByAttribute(self.layer.longName, 'CLEAR_SELECTION')
        except Exception:
            return

update_cursor

update_cursor(
    *field_names: FieldName,
    update_options: UpdateOptions | None = None,
    **overrides: Unpack[UpdateOptions],
) -> UpdateCursor

See Table.search_cursor doc for general info. Operation of this method is identical but returns an UpdateCursor

Source code in src/arcpie/featureclass.py
596
597
598
599
600
601
602
603
604
def update_cursor(self, *field_names: FieldName,
                update_options: UpdateOptions|None=None, 
                **overrides: Unpack[UpdateOptions]) -> UpdateCursor:
    """See `Table.search_cursor` doc for general info. Operation of this method is identical but returns an `UpdateCursor`"""
    if 'datum_transformation' in overrides and overrides['datum_transformation'] is None:
        overrides.pop('datum_transformation')
    if update_options and 'datum_transformation' in update_options and update_options['datum_transformation'] is None:
        update_options.pop('datum_transformation')
    return UpdateCursor(self.path, field_names, **self._resolve_update_options(update_options, overrides))

updater

updater(*fields: FieldName, strict: bool = False)

A wrapper around row_updater that allows use as a context manager

This simplifies the interaction with the row_updater method by allowing inline declaration of the generator. For most simple update operations, this manager should work well.

PARAMETER DESCRIPTION

fields

The fields to include in the update operation (default: All)

TYPE: FieldName | str DEFAULT: ()

stict

Raise a KeyError if an invalid fieldname is passed, otherwise drop invalid updates (default: False)

TYPE: bool

Example

with fc.editor, fc.updater() as upd: ... for row in upd: ... row['Name'] = 'Dave' ... upd.send(row)

Source code in src/arcpie/featureclass.py
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
@contextmanager
def updater(self, *fields: FieldName, strict: bool=False):
    """A wrapper around `row_updater` that allows use as a context manager

    This simplifies the interaction with the `row_updater` method by allowing inline declaration
    of the generator. For most simple update operations, this manager should work well. 

    Args:
        fields (FieldName|str): The fields to include in the update operation (default: All)
        stict (bool): Raise a KeyError if an invalid fieldname is passed, otherwise drop invalid updates (default: False)

    Example:
        >>> with fc.editor, fc.updater() as upd:
        ...     for row in upd:
        ...         row['Name'] = 'Dave'
        ...         upd.send(row)
    """
    try:
        yield self.row_updater(*(fields or self.fields), strict=strict)
    finally:
        pass

where

Apply a where clause to a Table or FeatureClass in a context

PARAMETER DESCRIPTION

where_clause

The where clause to apply to the Table or FeatureClass

TYPE: WhereClause | str

Example
>>> with fc.where("first = 'John'") as f:
...     for f in fc:
...         print(f)
{'first': 'John', 'last': 'Cleese', 'year': 1939}

>>> with fc.where('year > 1939'):
...     print(len(fc))
5
... print(len(fc))
6
Note

This method of filtering a Table or FeatureClass will always be more performant than using the .filter method. If you can achieve the filtering you want with a where clause, do it.

Source code in src/arcpie/featureclass.py
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
@contextmanager
def where(self, where_clause: WhereClause|str):
    """Apply a where clause to a Table or FeatureClass in a context

    Args:
        where_clause (WhereClause|str): The where clause to apply to the Table or FeatureClass

    Example:
        ```python
        >>> with fc.where("first = 'John'") as f:
        ...     for f in fc:
        ...         print(f)
        {'first': 'John', 'last': 'Cleese', 'year': 1939}

        >>> with fc.where('year > 1939'):
        ...     print(len(fc))
        5
        ... print(len(fc))
        6
        ```

    Note:
        This method of filtering a Table or FeatureClass will always be more performant than using the 
        `.filter` method. If you can achieve the filtering you want with a where clause, do it.
    """
    with self.options(
        search_options=SearchOptions(where_clause=str(where_clause)),
        update_options=UpdateOptions(where_clause=str(where_clause))):
        yield self

Table

Table(
    path: str | Path,
    *,
    search_options: SearchOptions | None = None,
    update_options: UpdateOptions | None = None,
    insert_options: InsertOptions | None = None,
    clause: SQLClause | None = None,
    where: str | None = None,
)

Bases: Generic[_Schema]

A Wrapper for ArcGIS Table objects

METHOD DESCRIPTION
__contains__

Implementation of contains that checks for a field existing in the FeatureClass

__eq__

Determine if the datasource of two featureclass objects is the same

__format__

Implement format specs for string formatting a featureclass.

__getitem__

Handle all defined overloads using pattern matching syntax

__iter__

Iterate all rows in the Table or FeatureClass yielding mappings of field name to field value

__len__

Iterate all rows and count them. Only count with self.search_options queries.

__repr__

Provide a constructor string e.g. Table or FeatureClass[Polygon]('path')

__str__

Return the Table or FeatureClass path for use with other arcpy methods

add_field

Add a new field to a Table or FeatureClass, if no type is provided, deafault of VARCHAR(255) is used

add_fields

Provide a mapping of fieldnames to Fields

add_to_map

Add the featureclass to a map

bind_to_layer

Update the provided layer's datasource to this Table or FeatureClass

clear

Clear all records from the table

copy

Create a new FeatureClass instance to prevent overriding a shared resource

copy_to

Copy this Table or FeatureClass to a new workspace

delete

Delete the object permanently using arcpy.management.Delete

delete_field

Delete a field from a Table or FeatureClass

delete_identical

Delete all records that have matching field values

delete_where

Delete all records that match the provided where clause

distinct

Yield rows of distinct values

exists

Check if the Table or FeatureClass actually exists (check for deletion or initialization with bad path)

fields_as

Override the default fields for the Table or FeatureClass so all non-explicit Iterators will

filter

Apply a function filter to rows in the Table or FeatureClass

from_layer

Build a Table or FeatureClass object from a layer applying the layer's current selection to the stored cursors

from_table

See from_layer for documentation, this is an alternative constructor that builds from a mp.Table object

get

Allow accessing the implemented indexes defined by __getitem__ with a default shielding a raised KeyError

get_records

Generate row dicts with in the form {field: value, ...} for each row in the cursor

get_schema

Get python code for the Table/FeatureClass schema

get_tuples

Generate tuple rows in the for (val1, val2, ...) for each row in the cursor

group_by

Group features by matching field values and yield full records in groups

has_field

Check if the field exists in the featureclass or is a valid Token (@[TOKEN])

insert_cursor

See Table.search_cursor doc for general info. Operation of this method is identical but returns an InsertCursor

insert_record

Insert a single record into the table

insert_records

Provide an iterable of records to insert

is_empty

Check if a Table/FeatureClass is empty

options

Enter a context block where the supplied options replace the stored options for the Table or FeatureClass

row_updater

A Bi-Directional generator that yields rows and updates them with the sent value

search_cursor

Get a SearchCursor for the Table or FeatureClass

select

If the Table or FeatureClass is bound to a layer, update the layer selection with the active SearchOptions

unselect

If the Table or FeatureClass is bound to a layer, Remove layer selection

update_cursor

See Table.search_cursor doc for general info. Operation of this method is identical but returns an UpdateCursor

updater

A wrapper around row_updater that allows use as a context manager

where

Apply a where clause to a Table or FeatureClass in a context

ATTRIBUTE DESCRIPTION
attribute_rules

Get an AttributeRuleManager object bound to the Table/FeatureClass

TYPE: AttributeRuleManager

clause

Default SQLClause

TYPE: SQLClause

da_describe

Access the da.Describe dictionary for the Table or FeatureClass

TYPE: dict[str, Any]

describe

Access the arcpy.Describe object for the Table or FeatureClass

TYPE: Table

editor

Get an Editor manager for the Table or FeatureClass

TYPE: Editor

field_defs

Get a mapping of Field properties to fieldnames

TYPE: dict[FieldName, Field]

fields

Tuple of all fieldnames in the Table or FeatureClass with OID@ as first

TYPE: tuple[TableToken | str, ...]

insert_options

Default InsertCursor options

TYPE: InsertOptions

layer

A Layer object for the FeatureClass/Table if one is bound

TYPE: Layer | None

name

The common name of the FeatureClass/Table

TYPE: str

np_dtypes

Numpy dtypes for each field

oid_field_name

ObjectID fieldname (ususally FID or OID or ObjectID)

TYPE: str

parent

The parent of the Table/FeatureClass (either a Dataset or a Database)

TYPE: str

path

The filepath of the FeatureClass/Table

TYPE: str

py_types

Get a mapping of fieldnames to python types for the Table

TYPE: dict[str, type]

search_options

Default SearchCursor options

TYPE: SearchOptions

subtype_field

The Subtype field (ususally SUBTYPE or SUBTYPE_CODE, etc.)

TYPE: str | None

subtypes

Result of ListSubtypes, mapping of code to Subtype object

TYPE: dict[int, Subtype]

update_options

Default UpdateCursor options

TYPE: UpdateOptions

workspace

Get the workspace of the Table or FeatureClass

TYPE: str

Source code in src/arcpie/featureclass.py
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
def __init__(
        self, path: str | Path,
        *,
        search_options: SearchOptions | None = None, 
        update_options: UpdateOptions | None = None, 
        insert_options: InsertOptions | None = None,
        clause: SQLClause | None = None,
        where: str | None = None,
    ) -> None:
    self._path = str(path)
    self._clause = clause or SQLClause(None, None)
    self._search_options = search_options or SearchOptions()
    self._insert_options = insert_options or InsertOptions()
    self._update_options = update_options or UpdateOptions()

    # Override
    if where:
        self._search_options['where_clause'] = where
        self._update_options['where_clause'] = where

    self._layer: Layer | None = None
    self._in_edit_session = False
    self._fields: tuple[TableToken | str, ...] | None = None
    self.all_fields: tuple[TableToken | str, ...] | None = None

attribute_rules property

attribute_rules: AttributeRuleManager

Get an AttributeRuleManager object bound to the Table/FeatureClass

clause property writable

clause: SQLClause

Default SQLClause

da_describe property

da_describe: dict[str, Any]

Access the da.Describe dictionary for the Table or FeatureClass

describe property

describe: Table

Access the arcpy.Describe object for the Table or FeatureClass

editor property

editor: Editor

Get an Editor manager for the Table or FeatureClass Will set multiuser_mode to True if the feature can version

field_defs property

field_defs: dict[FieldName, Field]

Get a mapping of Field properties to fieldnames

fields property

fields: tuple[TableToken | str, ...]

Tuple of all fieldnames in the Table or FeatureClass with OID@ as first

insert_options property writable

insert_options: InsertOptions

Default InsertCursor options

layer property writable

layer: Layer | None

A Layer object for the FeatureClass/Table if one is bound

name property

name: str

The common name of the FeatureClass/Table

np_dtypes property

np_dtypes

Numpy dtypes for each field

oid_field_name property

oid_field_name: str

ObjectID fieldname (ususally FID or OID or ObjectID)

parent property

parent: str

The parent of the Table/FeatureClass (either a Dataset or a Database)

path property

path: str

The filepath of the FeatureClass/Table

py_types property

py_types: dict[str, type]

Get a mapping of fieldnames to python types for the Table

search_options property writable

search_options: SearchOptions

Default SearchCursor options

subtype_field property

subtype_field: str | None

The Subtype field (ususally SUBTYPE or SUBTYPE_CODE, etc.)

subtypes property

subtypes: dict[int, Subtype]

Result of ListSubtypes, mapping of code to Subtype object

update_options property writable

update_options: UpdateOptions

Default UpdateCursor options

workspace property

workspace: str

Get the workspace of the Table or FeatureClass

__contains__

__contains__(field: str) -> bool

Implementation of contains that checks for a field existing in the FeatureClass

Source code in src/arcpie/featureclass.py
1191
1192
1193
1194
def __contains__(self, field: str) -> bool:
    """Implementation of contains that checks for a field existing in the `FeatureClass`
    """
    return field in self.fields

__eq__

__eq__(other: Any) -> bool

Determine if the datasource of two featureclass objects is the same

Source code in src/arcpie/featureclass.py
1244
1245
1246
def __eq__(self, other: Any) -> bool:
    """Determine if the datasource of two featureclass objects is the same"""
    return isinstance(other, self.__class__) and self.__fspath__() == other.__fspath__()

__format__

__format__(format_spec: str) -> str

Implement format specs for string formatting a featureclass.

Warning

The {fc:len} spec should only be used when needed. This spec will call __len__ when used and will traverse the entire Table or FeatureClass with applied SearchOptions each time it is called. See: __len__ doc for info on better ways to track counts in loops.

PARAMETER DESCRIPTION

format_spec

One of the options listed below (the | symbol is used to seperate aliases)

TYPE: str

PARAMETER DESCRIPTION
path|pth

Table or FeatureClass path

TYPE: str

len|length

Table or FeatureClass length (with applied SearchQuery)

TYPE: str

layer|lyr

Linked Table or FeatureClass layer if applicable (else 'None')

TYPE: str

shape|shp

Table or FeatureClass shape type

TYPE: str

units|unt

Table or FeatureClass linear unit name

TYPE: str

wkid|code

Table or FeatureClass WKID

TYPE: str

name|nm

Table or FeatureClass name

TYPE: str

fields|fld

Table or FeatureClass fields (comma seperated)

TYPE: str

Example
>>> f'{fc:wkid}'
'2236'
>>> f'{fc:path}'
'C:\<FeaturePath>'
>>> f'{fc:len}'
'101'
>>> f'{fc:shape}'
'Polygon'
Source code in src/arcpie/featureclass.py
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
def __format__(self, format_spec: str) -> str:
    """Implement format specs for string formatting a featureclass.

    Warning:
        The `{fc:len}` spec should only be used when needed. This spec will call `__len__` when 
        used and will traverse the entire Table or FeatureClass with applied SearchOptions each time it is 
        called. See: `__len__` doc for info on better ways to track counts in loops.

    Args:
        format_spec:  One of the options listed below (the `|` symbol is used to seperate aliases)

    Other Parameters:
        path|pth (str): Table or FeatureClass path
        len|length (str): Table or FeatureClass length (with applied SearchQuery)
        layer|lyr (str): Linked Table or FeatureClass layer if applicable (else `'None'`)
        shape|shp (str): Table or FeatureClass shape type
        units|unt (str): Table or FeatureClass linear unit name
        wkid|code (str): Table or FeatureClass WKID
        name|nm (str): Table or FeatureClass name
        fields|fld (str): Table or FeatureClass fields (comma seperated)

    Example:
        ```python
        >>> f'{fc:wkid}'
        '2236'
        >>> f'{fc:path}'
        'C:\\<FeaturePath>'
        >>> f'{fc:len}'
        '101'
        >>> f'{fc:shape}'
        'Polygon'
        ```
    """
    match format_spec:
        case 'path' | 'pth':
            return self.path
        case 'len' | 'length':
            return str(len(self))
        case 'layer' | 'lyr':
            return self.layer.longName if self.layer else 'None'
        case 'name' | 'nm':
            return self.name
        case 'fields' | 'flds':
            return ','.join(self.fields)
        case _:
            return str(self)

__getitem__

__getitem__(
    field: tuple[FieldName, ...],
) -> Iterator[tuple[Any, ...]]
__getitem__(field: list[FieldName]) -> Iterator[list[Any]]
__getitem__(field: set[FieldName]) -> Iterator[_Schema]
__getitem__(field: FieldName) -> Iterator[Any]
__getitem__(
    field: FilterFunc[_Schema],
) -> Iterator[_Schema]
__getitem__(field: WhereClause) -> Iterator[_Schema]
__getitem__(field: None) -> Iterator[None]
__getitem__(
    field: _IndexableTypes | FilterFunc[_Schema],
) -> Iterator[Any]

Handle all defined overloads using pattern matching syntax

PARAMETER DESCRIPTION

field

Yield values in the specified column (values only)

TYPE: str

field

Yield lists of values for requested columns (requested fields)

TYPE: list[str]

field

Yield tuples of values for requested columns (requested fields)

TYPE: tuple[str]

field

Yield dictionaries of values for requested columns (requested fields)

TYPE: set[str]

field

Yield rows that match function (all fields)

TYPE: FilterFunc

field

Yield rows that match clause (all fields)

TYPE: WhereClause

Example
>>> # Single Field
>>> print(list(fc['field']))
[val1, val2, val3, ...]

>>> # Field Tuple
>>> print(list(fc[('field1', 'field2')]))
[(val1, val2), (val1, val2), ...]

>>> # Field List
>>> print(list(fc[['field1', 'field2']]))
[[val1, val2], [val1, val2], ...]

>>> # Field Set (Row mapping limited to only requested fields)
>>> print(list(fc[{'field1', 'field2'}]))
[{'field1': val1, 'field2': val2}, {'field1': val1, 'field2': val2}, ...]

>>> # Last two options always return all fields in a mapping
>>> # Filter Function (passed to Table.filter())
>>> print(list(fc[lambda r: r['field1'] == target]))
[{'field1': val1, 'field2': val2, ...}, {'field1': val1, 'field2': val2, ...}, ...]

>>> # Where Clause (Use where() helper function or a WhereClause object)
>>> print(list(fc[where('field1 = target')]))
[{'field1': val1, 'field2': val2, ...}, {'field1': val1, 'field2': val2, ...}, ...]

>>> # None (Empty Iterator)
>>> print(list(fc[None]))
Source code in src/arcpie/featureclass.py
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
def __getitem__(self, field: _IndexableTypes | FilterFunc[_Schema]) -> Iterator[Any]:
    """Handle all defined overloads using pattern matching syntax

    Args:
        field (str): Yield values in the specified column (values only)
        field (list[str]): Yield lists of values for requested columns (requested fields)
        field (tuple[str]): Yield tuples of values for requested columns (requested fields)
        field (set[str]): Yield dictionaries of values for requested columns (requested fields)
        field (FilterFunc): Yield rows that match function (all fields)
        field (WhereClause): Yield rows that match clause (all fields)

    Example:
        ```python
        >>> # Single Field
        >>> print(list(fc['field']))
        [val1, val2, val3, ...]

        >>> # Field Tuple
        >>> print(list(fc[('field1', 'field2')]))
        [(val1, val2), (val1, val2), ...]

        >>> # Field List
        >>> print(list(fc[['field1', 'field2']]))
        [[val1, val2], [val1, val2], ...]

        >>> # Field Set (Row mapping limited to only requested fields)
        >>> print(list(fc[{'field1', 'field2'}]))
        [{'field1': val1, 'field2': val2}, {'field1': val1, 'field2': val2}, ...]

        >>> # Last two options always return all fields in a mapping
        >>> # Filter Function (passed to Table.filter())
        >>> print(list(fc[lambda r: r['field1'] == target]))
        [{'field1': val1, 'field2': val2, ...}, {'field1': val1, 'field2': val2, ...}, ...]

        >>> # Where Clause (Use where() helper function or a WhereClause object)
        >>> print(list(fc[where('field1 = target')]))
        [{'field1': val1, 'field2': val2, ...}, {'field1': val1, 'field2': val2, ...}, ...]

        >>> # None (Empty Iterator)
        >>> print(list(fc[None]))

        ```
    """
    match field:
        # Field Requests
        case str():
            with self.search_cursor(field) as cur:
                yield from (val for val, in cur)
        case tuple():
            with self.search_cursor(*field) as cur:
                yield from (row for row in cur)
        case list():
            with self.search_cursor(*field) as cur:
                yield from (list(row) for row in cur)
        case set():
            with self.search_cursor(*field) as cur:
                yield from (row for row in self.as_dict(cur))
        case None:
            yield from () # This allows a side effect None to be used to get nothing

        # Conditional Requests
        case wc if isinstance(wc, WhereClause):
            if self.all_fields and not wc.validate(self.all_fields):
                raise KeyError(f'Invalid Where Clause: {wc}, fields not found in {self.name}')
            with self.search_cursor(*self.fields, where_clause=wc.where_clause) as cur:
                yield from (row for row in self.as_dict(cur))
        case func if callable(func):
            yield from (row for row in self.filter(func))
        case _:
            raise KeyError(
                f"Invalid option: `{field}` "
                "Must be a WhereClause, filter functon, field, set of fields, list of fields, or tuple of fields"
            )

__iter__

__iter__() -> Iterator[_Schema]

Iterate all rows in the Table or FeatureClass yielding mappings of field name to field value

Note

It was decided to yield mappings because without specifying fields, it is up to the user to deal with the data as they see fit. Yielding tuples in an order that's not defined by the user would be confusing, so a mapping makes it clear exactly what they're accessing

Note

When a single field is specified using the fields_as context, values will be yielded

Source code in src/arcpie/featureclass.py
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
def __iter__(self) -> Iterator[_Schema]:
    """Iterate all rows in the Table or FeatureClass yielding mappings of field name to field value

    Note:
        It was decided to yield mappings because without specifying fields, it is up to the user
        to deal with the data as they see fit. Yielding tuples in an order that's not defined by
        the user would be confusing, so a mapping makes it clear exactly what they're accessing

    Note:
        When a single field is specified using the `fields_as` context, values will be yielded
    """ 
    with self.search_cursor(*self.fields) as cur:
        if len(self.fields) == 1:
            yield from (row for row, in cur)
        else:
            yield from self.as_dict(cur)

__len__

__len__() -> int

Iterate all rows and count them. Only count with self.search_options queries.

Note

The __format__('len') spec calls this function. So len(fc) and f'{fc:len}' are the same, with the caveat that the format spec option returns a string

Warning

This operation will traverse the whole dataset when called! You should not use it in loops:

# Bad
for i, _ in enumerate(fc):
    print(f'{i}/{len(fc)}')

# Good
count = len(fc)
for i, _ in enumerate(fc):
    print(f'{i}/{count}')

Source code in src/arcpie/featureclass.py
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
def __len__(self) -> int:
    """Iterate all rows and count them. Only count with `self.search_options` queries.

    Note:
        The `__format__('len')` spec calls this function. So `len(fc)` and `f'{fc:len}'` are the same, 
        with the caveat that the format spec option returns a string

    Warning:
        This operation will traverse the whole dataset when called! You should not use it in loops:
        ```python
        # Bad
        for i, _ in enumerate(fc):
            print(f'{i}/{len(fc)}')

        # Good
        count = len(fc)
        for i, _ in enumerate(fc):
            print(f'{i}/{count}')
        ```
    """
    #return sum(1 for _ in self['OID@'])
    return sum(1 for _ in self.search_cursor('OID@'))

__repr__

__repr__() -> str

Provide a constructor string e.g. Table or FeatureClass[Polygon]('path')

Source code in src/arcpie/featureclass.py
1236
1237
1238
def __repr__(self) -> str:
    """Provide a constructor string e.g. `Table or FeatureClass[Polygon]('path')`"""
    return f"{self.__class__.__name__}('{self.__fspath__()}')"

__str__

__str__() -> str

Return the Table or FeatureClass path for use with other arcpy methods

Source code in src/arcpie/featureclass.py
1240
1241
1242
def __str__(self) -> str:
    """Return the `Table` or `FeatureClass` path for use with other arcpy methods"""
    return self.__fspath__()

add_field

add_field(
    fieldname: str,
    field: Field | None = None,
    **options: Unpack[Field],
) -> None

Add a new field to a Table or FeatureClass, if no type is provided, deafault of VARCHAR(255) is used

PARAMETER DESCRIPTION

fieldname

The name of the new field (must not start with a number and be alphanum or underscored)

TYPE: str

field

A Field object that contains the desired field properties

TYPE: Field DEFAULT: None

**options

Allow passing keyword arguments for field directly (Overrides field arg)

TYPE: **Field DEFAULT: {}

Example
>>> new_field = Field(
...     field_alias='Abbreviated Month',
...     field_type='TEXT',
...     field_length='3',
...     field_domain='Months_ABBR',
... )

>>> print(fc.fields)
['OID@', 'SHAPE@', 'name', 'year']

>>> fc['month'] = new_field
>>> fc2['month'] = new_field # Can re-use a field definition 
>>> print(fc.fields)
['OID@', 'SHAPE@', 'name', 'year', 'month']
Source code in src/arcpie/featureclass.py
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
def add_field(self, fieldname: str, field: Field|None=None, **options: Unpack[Field]) -> None:
    """Add a new field to a Table or FeatureClass, if no type is provided, deafault of `VARCHAR(255)` is used

    Args:
        fieldname (str): The name of the new field (must not start with a number and be alphanum or underscored)
        field (Field): A Field object that contains the desired field properties
        **options (**Field): Allow passing keyword arguments for field directly (Overrides field arg)

    Example:
        ```python
        >>> new_field = Field(
        ...     field_alias='Abbreviated Month',
        ...     field_type='TEXT',
        ...     field_length='3',
        ...     field_domain='Months_ABBR',
        ... )

        >>> print(fc.fields)
        ['OID@', 'SHAPE@', 'name', 'year']

        >>> fc['month'] = new_field
        >>> fc2['month'] = new_field # Can re-use a field definition 
        >>> print(fc.fields)
        ['OID@', 'SHAPE@', 'name', 'year', 'month']
        ```
    """
    if self.has_field(fieldname):
        raise ValueError(f'{self.name} already has a field called {fieldname}!')

    # Use provided field or default to 'TEXT' and override with kwargs
    field = {**(field or Field(field_type='TEXT')), **options}

    # Handle malformed Field arg
    field['field_type'] = field.get('field_type', 'TEXT')

    _option_kwargs = set(Field.__optional_keys__) | set(Field.__required_keys__)
    _provided = set(field.keys())

    if not _provided <= _option_kwargs:
        raise ValueError(f"Unknown Field properties provided: {_provided - _option_kwargs}")

    if not valid_field(fieldname):
        raise ValueError(
            f"{fieldname} is invalid, fieldnames must not start with a number "
            "and must only contain alphanumeric characters and underscores"
        )

    default = field.pop('field_default') if 'field_default' in field else None        
    with EnvManager(workspace=self.workspace):
        AddField(self.path, fieldname, **field) # type: ignore (field_default is popped for alteration)
        self._fields = None
        if default is not None:
            AssignDefaultToField(self.path, fieldname, default_value=default)

add_fields

add_fields(fields: dict[str, Field]) -> None

Provide a mapping of fieldnames to Fields

PARAMETER DESCRIPTION

fields

A mapping of fieldnames to Field objects

TYPE: dict[str, Field]

Example
>>> fields = {'f1': Field(...), 'f2': Field(...)}
>>> fc.add_fields(fields)
>>> fc.fields
['OID@', 'SHAPE@', 'f1', 'f2']
Source code in src/arcpie/featureclass.py
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
def add_fields(self, fields: dict[str, Field]) -> None:
    """Provide a mapping of fieldnames to Fields

    Args:
        fields (dict[str, Field]): A mapping of fieldnames to Field objects

    Example:
        ```python
        >>> fields = {'f1': Field(...), 'f2': Field(...)}
        >>> fc.add_fields(fields)
        >>> fc.fields
        ['OID@', 'SHAPE@', 'f1', 'f2']
        ```
    """
    for fieldname, field in fields.items():
        self.add_field(fieldname, field)

add_to_map

add_to_map(
    map: Map,
    pos: Literal[
        "AUTO_ARRANGE", "BOTTOM", "TOP"
    ] = "AUTO_ARRANGE",
) -> None

Add the featureclass to a map

Note

If the Table or FeatureClass has a layer, the bound layer will be added to the map. Otherwise a default layer will be added. And the new layer will be bound to the Table or FeatureClass

PARAMETER DESCRIPTION

map

The map to add the featureclass to

TYPE: Map

Source code in src/arcpie/featureclass.py
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
def add_to_map(self, map: Map, pos: Literal['AUTO_ARRANGE', 'BOTTOM', 'TOP']='AUTO_ARRANGE') -> None:
    """Add the featureclass to a map

    Note: 
        If the Table or FeatureClass has a layer, the bound layer will be added to the map. 
        Otherwise a default layer will be added. And the new layer will be bound to the Table or FeatureClass

    Args:
        map (Map): The map to add the featureclass to
    """
    if not self.layer:
        # Create a default layer, bind it, remove, and add back
        # with addLayer to match behavior with existing bound layer
        self.layer = map.addDataFromPath(self.path) #type:ignore (Always Layer)
        map.removeLayer(self.layer) #type:ignore (Incorrect Signature)
    map.addLayer(self.layer, pos) #type:ignore

bind_to_layer

bind_to_layer(layer: Layer) -> None

Update the provided layer's datasource to this Table or FeatureClass

PARAMETER DESCRIPTION

layer

The layer to update connection properties for

TYPE: Layer

Raises: ValueError

Source code in src/arcpie/featureclass.py
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
def bind_to_layer(self, layer: Layer) -> None:
    """Update the provided layer's datasource to this Table or FeatureClass

    Args:
        layer (Layer): The layer to update connection properties for

    Raises: ValueError
    """
    # Use the wrapped Layer from arcpie.project so workspace can be inferred
    from .project import Layer as _Layer
    layer = _Layer(layer)
    # Try to update datasource using updateConnectionProperties
    try:
        layer.updateConnectionProperties(layer.feature_class.workspace, self.workspace)
        return
    except:
        pass

    # Fallback to direct CIM update (updateConnectionProperties is buggy)
    # TODO: Integrate cimple.cim here for typing
    try:
        definition = layer.cim
        dc = definition.featureTable.dataConnection # type: ignore
        dc.workspaceConnectionString = f'DATABASE={self.workspace}'
        dc.dataset = self.name
        # Remove missing FeatureDataset subpaths
        if dc.featureDataset and dc.featureDataset not in Path(self.path).parts: # type: ignore
            dc.featureDataset = None
        layer.setDefinition(definition) # type: ignore
    except Exception as e:
        raise ValueError(f'Unable to bind to layer: {e}')

clear

clear() -> None

Clear all records from the table

Source code in src/arcpie/featureclass.py
1031
1032
1033
1034
1035
def clear(self) -> None:
    """Clear all records from the table"""
    with self.update_cursor(self.oid_field_name) as cur:
        for _ in cur:
            cur.deleteRow()

copy

copy() -> Table[_Schema]

Create a new FeatureClass instance to prevent overriding a shared resource

Source code in src/arcpie/featureclass.py
1615
1616
1617
1618
1619
1620
1621
1622
1623
def copy(self) -> Table[_Schema]:
    """Create a new FeatureClass instance to prevent overriding a shared resource"""
    return Table[_Schema](
        self._path, 
        search_options=self.search_options.copy(),
        update_options=self.update_options.copy(),
        insert_options=self.insert_options.copy(),
        clause=self.clause
    )

copy_to

copy_to(workspace: str, options: bool = True) -> Self

Copy this Table or FeatureClass to a new workspace

PARAMETER DESCRIPTION

workspace

The path to the workspace

TYPE: str

options

Copy the cursor options to the new Table or FeatureClass (default: True)

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
Table or FeatureClass

A Table or FeatureClass instance of the copied features

Example
>>> new_fc = fc.copy('workspace2')
>>> new_fc == fc
False
Source code in src/arcpie/featureclass.py
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
def copy_to(self, workspace: str, options: bool=True) -> Self:
    """Copy this `Table` or `FeatureClass` to a new workspace

    Args:
        workspace (str): The path to the workspace
        options (bool): Copy the cursor options to the new `Table` or `FeatureClass` (default: `True`)

    Returns:
        (Table or FeatureClass): A `Table` or `FeatureClass` instance of the copied features

    Example:
        ```python
        >>> new_fc = fc.copy('workspace2')
        >>> new_fc == fc
        False
        ```
    """
    #name = Path(self.path).relative_to(Path(self.workspace))
    if Exists(copy_fc := Path(workspace) / self.name):
        raise ValueError(f'{self.name} already exists in {workspace}!')
    CopyFeatures(self.path, str(copy_fc))
    fc = self.__class__(str(copy_fc))
    if options:
        fc.search_options = self.search_options
        fc.update_options = self.update_options
        fc.insert_options = self.insert_options
        fc.clause = self.clause
    return fc

delete

delete() -> None

Delete the object permanently using arcpy.management.Delete

Note: After calling this method, the current FeatureClass object becomes unbound so del is called on self

Source code in src/arcpie/featureclass.py
877
878
879
880
881
882
883
def delete(self) -> None:
    """Delete the object permanently using arcpy.management.Delete

    Note: After calling this method, the current FeatureClass object becomes unbound so `del` is called on `self`
    """
    Delete(self.path)
    del self

delete_field

delete_field(fieldname: str) -> None

Delete a field from a Table or FeatureClass

PARAMETER DESCRIPTION

fieldname

The name of the field to delete/drop

TYPE: str

Example
>>> print(fc.fields)
['OID@', 'SHAPE@', 'name', 'year', 'month']

>>> del fc['month']
>>> print(fc.fields)
['OID@', 'SHAPE@', 'name', 'year']
>>> fc.delete_field('year')
>>> print(fc.fields)
['OID@', 'SHAPE@', 'name']
Source code in src/arcpie/featureclass.py
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
def delete_field(self, fieldname: str) -> None:
    """Delete a field from a Table or FeatureClass

    Args:
        fieldname (str): The name of the field to delete/drop

    Example:
        ```python
        >>> print(fc.fields)
        ['OID@', 'SHAPE@', 'name', 'year', 'month']

        >>> del fc['month']
        >>> print(fc.fields)
        ['OID@', 'SHAPE@', 'name', 'year']
        >>> fc.delete_field('year')
        >>> print(fc.fields)
        ['OID@', 'SHAPE@', 'name']
        ```
    """
    if fieldname in self.Tokens:
        raise ValueError(f"{fieldname} is a Token and cannot be deleted!")
    if not self.has_field(fieldname):
        raise ValueError(f"{fieldname} does not exist in {self.name}")
    with EnvManager(workspace=self.workspace):
        DeleteField(self.path, fieldname)
        self._fields = None # Defer new field check to next access

delete_identical

delete_identical(
    field_names: Iterable[FieldName] | FieldName,
) -> dict[int, int]

Delete all records that have matching field values

PARAMETER DESCRIPTION

field_names

The fields used to define an identical feature

TYPE: Sequence[FieldName] | FieldName

RETURNS DESCRIPTION
dict[int, int]

A dictionary of count of identical features deleted per feature

Note

Insertion order takes precidence unless the Table or FeatureClass is ordered. The first feature found by the cursor will be maintained and all subsequent matches will be removed

Source code in src/arcpie/featureclass.py
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
def delete_identical(self, field_names: Iterable[FieldName] | FieldName) -> dict[int, int]:
    """Delete all records that have matching field values

    Args:
        field_names (Sequence[FieldName] | FieldName): The fields used to define an identical feature

    Returns:
        (dict[int, int]): A dictionary of count of identical features deleted per feature

    Note:
        Insertion order takes precidence unless the Table or FeatureClass is ordered. The first feature found
        by the cursor will be maintained and all subsequent matches will be removed
    """
    # All
    if isinstance(field_names, str):
        field_names = [field_names]

    unique: dict[int, tuple[Any]] = {}
    deleted: dict[int, int] = {}
    with self.update_cursor('OID@', *field_names) as cur:
        for row in cur:
            oid: int = row[0]
            row = tuple(row[1:])
            for match_id, match_row in unique.items():
                if all(a == b for a, b in zip(row, match_row)):
                    match = match_id
                    break
            else:
                match = False

            if not match:
                unique[oid] = row

            else:
                deleted.setdefault(match, 0)
                deleted[match] += 1
                cur.deleteRow()
    return deleted

delete_where

delete_where(clause: WhereClause | str) -> None

Delete all records that match the provided where clause

PARAMETER DESCRIPTION

clause

The SQL query that determines the records that will be deleted

TYPE: WhereClause | str

Source code in src/arcpie/featureclass.py
1037
1038
1039
1040
1041
1042
1043
1044
def delete_where(self, clause: WhereClause|str) -> None:
    """Delete all records that match the provided where clause

    Args:
        clause (WhereClause|str): The SQL query that determines the records that will be deleted
    """
    with self.where(clause):
        self.clear()

distinct

Yield rows of distinct values

PARAMETER DESCRIPTION

distinct_fields

The field or fields to find distinct values for. Choosing multiple fields will find all distinct instances of those field combinations

TYPE: FieldOpt

YIELDS DESCRIPTION
tuple[Any, ...]

A tuple containing the distinct values (single fields will yield (value, ) tuples)

Source code in src/arcpie/featureclass.py
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
def distinct(self, distinct_fields: Iterable[FieldName] | FieldName) -> Iterator[tuple[Any, ...]]:
    """Yield rows of distinct values

    Args:
        distinct_fields (FieldOpt): The field or fields to find distinct values for.
            Choosing multiple fields will find all distinct instances of those field combinations

    Yields:
        ( tuple[Any, ...] ): A tuple containing the distinct values (single fields will yield `(value, )` tuples)
    """
    clause = SQLClause(prefix=f'DISTINCT {format_query_list(distinct_fields)}', postfix=None)
    try:
        yield from (value for value in self.search_cursor(*distinct_fields, sql_clause=clause))
    except RuntimeError: # Fallback when DISTINCT is not available or fails with Token input
        yield from sorted(set(self.get_tuples(distinct_fields)))

exists

exists() -> bool

Check if the Table or FeatureClass actually exists (check for deletion or initialization with bad path)

Source code in src/arcpie/featureclass.py
914
915
916
def exists(self) -> bool:
    """Check if the Table or FeatureClass actually exists (check for deletion or initialization with bad path)"""
    return Exists(str(self))

fields_as

fields_as(*fields: FieldName)

Override the default fields for the Table or FeatureClass so all non-explicit Iterators will only yield these fields (e.g. for row in fc: ...)

PARAMETER DESCRIPTION

*fields

Varargs of the fieldnames to limit all unspecified Iterators to

TYPE: FieldName DEFAULT: ()

Example
>>> with fc.fields_as('OID@', 'NAME'):
...     for row in fc:
...         print(row)
{'OID@': 1, 'NAME': 'John'}
{'OID@': 2, 'NAME': 'Michael'}
...
>>> for row in fc:
...     print(row)
{'OID@': 1, 'NAME': 'John', 'AGE': 75, 'ADDRESS': 123 Silly Walk}
{'OID@': 2, 'NAME': 'Michael', 'AGE': 70, 'ADDRESS': 42 Dead Parrot Blvd}
...
Source code in src/arcpie/featureclass.py
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
@contextmanager
def fields_as(self, *fields: FieldName):
    """Override the default fields for the Table or FeatureClass so all non-explicit Iterators will
    only yield these fields (e.g. `for row in fc: ...`)

    Args:
        *fields (FieldName): Varargs of the fieldnames to limit all unspecified Iterators to

    Example:
        ```python
        >>> with fc.fields_as('OID@', 'NAME'):
        ...     for row in fc:
        ...         print(row)
        {'OID@': 1, 'NAME': 'John'}
        {'OID@': 2, 'NAME': 'Michael'}
        ...
        >>> for row in fc:
        ...     print(row)
        {'OID@': 1, 'NAME': 'John', 'AGE': 75, 'ADDRESS': 123 Silly Walk}
        {'OID@': 2, 'NAME': 'Michael', 'AGE': 70, 'ADDRESS': 42 Dead Parrot Blvd}
        ...
        ```
    """
    # Allow passing a single field as a string `fc.fields_as('OID@')` to maintain
    # The call format of *Cursor objects
    _fields = self.fields
    self._fields = tuple(fields)
    try:
        yield self
    finally:
        self._fields = _fields

filter

filter(
    func: FilterFunc[_Schema], invert: bool = False
) -> Iterator[_Schema]

Apply a function filter to rows in the Table or FeatureClass

PARAMETER DESCRIPTION

func

A callable that takes a row dictionary and returns True or False

TYPE: Callable[[dict[str, Any]], bool]

invert

Invert the function. Only yield rows that return False

TYPE: bool DEFAULT: False

YIELDS DESCRIPTION
dict[str, Any]

Rows in the Table or FeatureClass that match the filter (or inverted filter)

Example
>>> def area_filter(row: dict) -> bool:
>>>     return row['Area'] >= 10

>>> for row in fc:
>>>     print(row['Area'])
1
2
10
<etc>

>>> for row in fc.filter(area_filter):
>>>     print(row['Area'])
10
11
90
<etc>
Source code in src/arcpie/featureclass.py
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
def filter(self, func: FilterFunc[_Schema], invert: bool=False) -> Iterator[_Schema]:
    """Apply a function filter to rows in the Table or FeatureClass

    Args:
        func (Callable[[dict[str, Any]], bool]): A callable that takes a 
            row dictionary and returns True or False
        invert (bool): Invert the function. Only yield rows that return `False`

    Yields:
        ( dict[str, Any] ): Rows in the Table or FeatureClass that match the filter (or inverted filter)

    Example:
        ```python
        >>> def area_filter(row: dict) -> bool:
        >>>     return row['Area'] >= 10

        >>> for row in fc:
        >>>     print(row['Area'])
        1
        2
        10
        <etc>

        >>> for row in fc.filter(area_filter):
        >>>     print(row['Area'])
        10
        11
        90
        <etc>
        ```

    """
    if hasattr(func, 'fields'): # Allow decorated filters for faster iteration (see `filter_fields`)
        with self.fields_as(*getattr(func, 'fields')):
            yield from (row for row in self if func(row) == (not invert))
    else:
        yield from (row for row in self if func(row) == (not invert))

from_layer classmethod

from_layer(
    layer: Layer,
    *,
    ignore_selection: bool = False,
    ignore_def_query: bool = False,
) -> Table[Any]

Build a Table or FeatureClass object from a layer applying the layer's current selection to the stored cursors

PARAMETER DESCRIPTION

layer

The layer to convert to a Table or FeatureClass

TYPE: Layer

ignore_selection

Ignore the layer selection (default: False)

TYPE: bool DEFAULT: False

ignore_def_query

Ignore the layer definition query (default: False)

TYPE: bool DEFAULT: False

Returns: ( Table or FeatureClass ): The Table or FeatureClass object with the layer query applied

Source code in src/arcpie/featureclass.py
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
@classmethod
def from_layer(cls, layer: Layer,
               *,
               ignore_selection: bool = False,
               ignore_def_query: bool = False,) -> Table[Any]:
    """Build a Table or FeatureClass object from a layer applying the layer's current selection to the stored cursors

    Args:
        layer (Layer): The layer to convert to a Table or FeatureClass
        ignore_selection (bool): Ignore the layer selection (default: False)
        ignore_def_query (bool): Ignore the layer definition query (default: False)
    Returns:
        ( Table or FeatureClass ): The Table or FeatureClass object with the layer query applied
    """
    fc = cls(Path(layer.dataSource).resolve())

    selected_ids: set[int] | None = (
        layer.getSelectionSet() or None
        if not ignore_selection 
        else None
    )
    definition_query: str|None = (
        layer.definitionQuery or None
        if not ignore_def_query 
        else None
    )
    selection: str|None = (
        f"{fc.oid_field_name} IN ({format_query_list(selected_ids)})" 
        if selected_ids 
        else None
    )

    if (query_components := list(filter(None, [definition_query, selection]))):
        where_clause = ' AND '.join(query_components)
        fc.search_options = SearchOptions(where_clause=where_clause)
        fc.update_options = UpdateOptions(where_clause=where_clause)

    fc.layer = layer
    return fc

from_table classmethod

from_table(
    table: Table,
    *,
    ignore_selection: bool = False,
    ignore_def_query: bool = False,
) -> Table

See from_layer for documentation, this is an alternative constructor that builds from a mp.Table object

Source code in src/arcpie/featureclass.py
1567
1568
1569
1570
1571
1572
1573
@classmethod
def from_table(cls, table: TableLayer,
               *,
               ignore_selection: bool = False,
               ignore_def_query: bool = False,) -> Table:
    """See `from_layer` for documentation, this is an alternative constructor that builds from a mp.Table object"""
    return Table.from_layer(table, ignore_selection=ignore_selection, ignore_def_query=ignore_def_query) # type: ignore (this won't break the interface)

get

get(
    field: tuple[FieldName, ...], default: _T
) -> Iterator[tuple[Any, ...]] | _T
get(
    field: list[FieldName], default: _T
) -> Iterator[list[Any]] | _T
get(
    field: set[FieldName], default: _T
) -> Iterator[_Schema] | _T
get(field: FieldName, default: _T) -> Iterator[Any] | _T
get(
    field: FilterFunc[_Schema], default: _T
) -> Iterator[_Schema] | _T
get(
    field: WhereClause, default: _T
) -> Iterator[_Schema] | _T
get(field: None, default: _T) -> Iterator[None] | _T
get(
    field: _IndexableTypes | FilterFunc[_Schema],
    default: _T = None,
) -> Iterator[Any] | _T

Allow accessing the implemented indexes defined by __getitem__ with a default shielding a raised KeyError

PARAMETER DESCRIPTION

field

The index to check (see __getitem__ implementations)

TYPE: _Indexable_Types

default

A default to return when the indexing raises a KeyError or cursor field RuntimeError (default: None)

TYPE: _T DEFAULT: None

Example
>>> for name, age in fc[('Name', 'Age')]:
>>>     print(name, age)
...
KeyError "Name"
...

>>> for name, age in fc.get(('Name', 'Age'), [])
Source code in src/arcpie/featureclass.py
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
def get(self, field: _IndexableTypes | FilterFunc[_Schema], default: _T=None) -> Iterator[Any] | _T:
    """Allow accessing the implemented indexes defined by `__getitem__` with a default shielding a raised `KeyError`

    Args:
        field (_Indexable_Types): The index to check (see `__getitem__` implementations)
        default (_T): A default to return when the indexing raises a `KeyError` or cursor field `RuntimeError` (default: None)

    Example:
        ```python
        >>> for name, age in fc[('Name', 'Age')]:
        >>>     print(name, age)
        ...
        KeyError "Name"
        ...

        >>> for name, age in fc.get(('Name', 'Age'), [])
        ```

    """
    try:
        return self[field]
    except (KeyError , RuntimeError) as e:
        if isinstance(e, RuntimeError) and 'Cannot find field' not in str(e):
            raise # Raise any non field related RuntimeErrors
        return default

get_records

Generate row dicts with in the form {field: value, ...} for each row in the cursor

PARAMETER DESCRIPTION

field_names

The columns to iterate

TYPE: str | Iterable[str]

**options

Additional options to pass on to the cursor

TYPE: Unpack[SearchOptions] DEFAULT: {}

Yields ( dict[str, Any] ): A mapping of fieldnames to field values for each row

Source code in src/arcpie/featureclass.py
738
739
740
741
742
743
744
745
746
747
748
def get_records(self, field_names: Iterable[FieldName] | FieldName, **options: Unpack[SearchOptions]) -> Iterator[_Schema]:
    """Generate row dicts with in the form `{field: value, ...}` for each row in the cursor

    Args:
        field_names (str | Iterable[str]): The columns to iterate
        **options (Unpack[SearchOptions]): Additional options to pass on to the cursor
    Yields 
        ( dict[str, Any] ): A mapping of fieldnames to field values for each row
    """
    with self.search_cursor(*field_names, **options) as cur:
        yield from self.as_dict(cur)

get_schema

get_schema(
    *,
    fallback_type: type = object,
    docs: dict[str, str] | None = None,
    include_shape_token: bool = True,
    include_oid_token: bool = True,
    default_doc: Callable[[Field], str]
    | None
    | Literal["nodoc"] = None,
) -> str

Get python code for the Table/FeatureClass schema

    Args:
        fallback_type: The default type annotation for any fields that aren't mapped properly
        docs: Optional docs to include for each field (e.g. `{'FieldName': 'field doc', ...}`)
        include_shape_token: Include a `SHAPE@` key with the FeatureClass shape type (no effect on Tables)
        include_oid_token: Include the `OID@` key
        default_doc: A function that takes a Field dictionary and retuens a formatted doc (default: `k: v

...`)

Source code in src/arcpie/featureclass.py
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
def get_schema(self,
               *,
               fallback_type: type = object,
               docs: dict[str, str] | None = None,
               include_shape_token: bool = True,
               include_oid_token: bool = True,
               default_doc: Callable[[Field], str] | None | Literal['nodoc'] = None
) -> str:
    """Get python code for the Table/FeatureClass schema

    Args:
        fallback_type: The default type annotation for any fields that aren't mapped properly
        docs: Optional docs to include for each field (e.g. `{'FieldName': 'field doc', ...}`)
        include_shape_token: Include a `SHAPE@` key with the FeatureClass shape type (no effect on Tables)
        include_oid_token: Include the `OID@` key
        default_doc: A function that takes a Field dictionary and retuens a formatted doc (default: `k: v\n\n...`)
    """
    # Deferred Import since yield_schema does an instance check on FeatureClass/Table
    from arcpie.schema.field import yield_schema
    if default_doc is None:
        return '\n'.join(
            yield_schema(
                self, 
                fallback_type=fallback_type, 
                docs=docs, 
                include_oid_token=include_oid_token, 
                include_shape_token=include_shape_token
            )
        )
    else:
        return '\n'.join(
            yield_schema(
                self, 
                fallback_type=fallback_type, 
                docs=docs, 
                include_oid_token=include_oid_token, 
                include_shape_token=include_shape_token,
                # Only override the default formatter if None is given
                # if Literal 'nodoc' is supplied, use closure with empty string as doc func
                default_doc=(lambda f: '') if default_doc == 'nodoc' else default_doc
            )
        )

get_tuples

Generate tuple rows in the for (val1, val2, ...) for each row in the cursor

PARAMETER DESCRIPTION

field_names

The columns to iterate

TYPE: str | Iterable[str]

**options

Additional parameters to pass to the SearchCursor

TYPE: SearchOptions DEFAULT: {}

Source code in src/arcpie/featureclass.py
750
751
752
753
754
755
756
757
758
def get_tuples(self, field_names: Iterable[FieldName] | FieldName, **options: Unpack[SearchOptions]) -> Iterator[tuple[Any, ...]]:
    """Generate tuple rows in the for (val1, val2, ...) for each row in the cursor

    Args:
        field_names (str | Iterable[str]): The columns to iterate
        **options (SearchOptions): Additional parameters to pass to the SearchCursor
    """
    with self.search_cursor(*field_names, **options) as cur:
        yield from cur

group_by

group_by(
    group_fields: Sequence[FieldName] | FieldName,
    return_fields: Sequence[FieldName] | FieldName = "*",
) -> Iterator[tuple[GroupIdent, GroupIter]]

Group features by matching field values and yield full records in groups

PARAMETER DESCRIPTION

group_fields

The fields to group the data by

TYPE: FieldOpt

return_fields

The fields to include in the output record ('*' means all and is default)

TYPE: FieldOpt DEFAULT: '*'

Yields: ( Iterator[tuple[tuple[FieldName, ...], Iterator[tuple[Any, ...] | Any]]] ): A nested iterator of groups and then rows

Example
>>> # With a field group, you will be able to unpack the tuple
>>> for group, rows in fc.group_by(['GroupField1', 'GroupField2'], ['ValueField1', 'ValueField2', ...]):
...     print(group)
...     for v1, v2 in rows:
...        if v1 > 10:
...            print(v2)
(GroupValue1A, GroupValue1B)
valueA
valueB
...
>>> # With a single field, you will have direct access to the field values   
>>> for group, district_populations in fc.group_by(['City', 'State'], 'Population'):
>>>         print(f"{group}: {sum(district_populations)}")
(New York, NY): 8260000
(Boston, MA): 4941632
...
Source code in src/arcpie/featureclass.py
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
def group_by(self, group_fields: Sequence[FieldName] | FieldName, return_fields: Sequence[FieldName] | FieldName ='*') -> Iterator[tuple[GroupIdent, GroupIter]]:
    """Group features by matching field values and yield full records in groups

    Args:
        group_fields (FieldOpt): The fields to group the data by
        return_fields (FieldOpt): The fields to include in the output record (`'*'` means all and is default)
    Yields:
        ( Iterator[tuple[tuple[FieldName, ...], Iterator[tuple[Any, ...] | Any]]] ): A nested iterator of groups and then rows

    Example:
        ```python
        >>> # With a field group, you will be able to unpack the tuple
        >>> for group, rows in fc.group_by(['GroupField1', 'GroupField2'], ['ValueField1', 'ValueField2', ...]):
        ...     print(group)
        ...     for v1, v2 in rows:
        ...        if v1 > 10:
        ...            print(v2)
        (GroupValue1A, GroupValue1B)
        valueA
        valueB
        ...
        >>> # With a single field, you will have direct access to the field values   
        >>> for group, district_populations in fc.group_by(['City', 'State'], 'Population'):
        >>>         print(f"{group}: {sum(district_populations)}")
        (New York, NY): 8260000
        (Boston, MA): 4941632
        ...
        ```
    """

    # Parameter Validations
    if isinstance(group_fields, str):
        group_fields = (group_fields,)
    if return_fields == '*':
        return_fields = self.fields
    if isinstance(return_fields, str):
        return_fields = (return_fields,)
    if len(group_fields) < 1 or len(return_fields) < 1:
        raise ValueError("Group Fields and Return Fields must be populated")

    group_fields = list(group_fields)
    return_fields = list(return_fields)
    _all_fields = group_fields + return_fields
    for group in self.distinct(group_fields):
        group_key = {field : value for field, value in zip(group_fields, group)}
        where_clause = " AND ".join(f"{field} = {norm(value)}" for field, value in group_key.items())
        if '@' not in where_clause: # Handle valid clause (no tokens)
            with self.search_cursor(*return_fields, where_clause=where_clause) as group_cur:
                yield (extract_singleton(group), (extract_singleton(row) for row in group_cur))
        else: # Handle token being passed by iterating a cursor and checking values directly
            for row in filter(lambda row: all(row[k] == group_key[k] for k in group_key), self[set(_all_fields)]):
                yield (extract_singleton(group), (row.pop(k) for k in return_fields)) # type: ignore (TypedDict Generic causes issues)

has_field

has_field(fieldname: str) -> bool

Check if the field exists in the featureclass or is a valid Token (@[TOKEN])

Source code in src/arcpie/featureclass.py
925
926
927
def has_field(self, fieldname: str) -> bool:
    """Check if the field exists in the featureclass or is a valid Token (@[TOKEN])"""
    return fieldname in self.fields or fieldname in self.Tokens

insert_cursor

insert_cursor(
    *field_names: FieldName,
    insert_options: InsertOptions | None = None,
    **overrides: Unpack[InsertOptions],
) -> InsertCursor

See Table.search_cursor doc for general info. Operation of this method is identical but returns an InsertCursor

Source code in src/arcpie/featureclass.py
586
587
588
589
590
591
592
593
594
def insert_cursor(self, *field_names: FieldName,
                  insert_options: InsertOptions|None=None, 
                  **overrides: Unpack[InsertOptions]) -> InsertCursor:
    """See `Table.search_cursor` doc for general info. Operation of this method is identical but returns an `InsertCursor`"""
    if 'datum_transformation' in overrides and overrides['datum_transformation'] is None:
        overrides.pop('datum_transformation')
    if insert_options and 'datum_transformation' in insert_options and insert_options['datum_transformation'] is None:
        insert_options.pop('datum_transformation')
    return InsertCursor(self.path, field_names, **self._resolve_insert_options(insert_options, overrides))

insert_record

insert_record(
    record: _Schema, ignore_errors: bool = False
) -> int | None

Insert a single record into the table

Source code in src/arcpie/featureclass.py
760
761
762
763
764
765
766
767
768
def insert_record(self, record: _Schema, ignore_errors: bool=False) -> int | None:
    """Insert a single record into the table"""
    if missing_fields := set(record.keys()).difference(self.fields):
        if ignore_errors:
            return None
        else:
            raise ValueError(f'{missing_fields} not in {self.fields}')
    with self.insert_cursor(*record.keys()) as cur:
        return cur.insertRow(list(record.values()))

insert_records

insert_records(
    records: Iterable[_Schema], ignore_errors: bool = False
) -> Iterator[int]

Provide an iterable of records to insert Args: records (Iterable[RowRecord]): The sequence of records to insert ignore_errors (bool): Ignore per-row errors and continue. Otherwise raise KeyError (default: True)

RETURNS DESCRIPTION
Iterator[int]

Returns the OIDs of the newly inserted rows

RAISES DESCRIPTION
KeyError

If the records have varying keys or the keys are not in the Table or FeatureClass

Example
>>> new_rows = [
...    {'first': 'John', 'last': 'Cleese', 'year': 1939}, 
...    {'first': 'Michael', 'last': 'Palin', 'year': 1943}
... ]
>>> print(fc.insert_rows(new_rows))
(2,3)

>>> # Insert all shapes from fc into fc2
>>> fc2.insert_rows(fc.get_records(['first', 'last', 'year']))
(1,2)
Source code in src/arcpie/featureclass.py
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
def insert_records(self, records: Iterable[_Schema] , ignore_errors: bool=False) -> Iterator[int]:
    """Provide an iterable of records to insert
    Args:
        records (Iterable[RowRecord]): The sequence of records to insert
        ignore_errors (bool): Ignore per-row errors and continue. Otherwise raise KeyError (default: True)

    Returns:
        ( Iterator[int] ): Returns the OIDs of the newly inserted rows

    Raises:
        ( KeyError ): If the records have varying keys or the keys are not in the Table or FeatureClass

    Example:
        ```python
        >>> new_rows = [
        ...    {'first': 'John', 'last': 'Cleese', 'year': 1939}, 
        ...    {'first': 'Michael', 'last': 'Palin', 'year': 1943}
        ... ]
        >>> print(fc.insert_rows(new_rows))
        (2,3)

        >>> # Insert all shapes from fc into fc2
        >>> fc2.insert_rows(fc.get_records(['first', 'last', 'year']))
        (1,2)
        ```
    """
    yield from filter(None, (self.insert_record(record, ignore_errors=ignore_errors) for record in records))

is_empty

is_empty() -> bool

Check if a Table/FeatureClass is empty

Source code in src/arcpie/featureclass.py
918
919
920
921
922
923
def is_empty(self) -> bool:
    """Check if a Table/FeatureClass is empty"""
    for _ in self:
        return False
    else:
        return True

options

options(
    *,
    strict: bool = False,
    search_options: SearchOptions | None = None,
    update_options: UpdateOptions | None = None,
    insert_options: InsertOptions | None = None,
    clause: SQLClause | None = None,
)

Enter a context block where the supplied options replace the stored options for the Table or FeatureClass

PARAMETER DESCRIPTION

strict

If this is set to True the Table or FeatureClass will not fallback on existing options when set to False, provided options override existing options (default: False)

TYPE: bool DEFAULT: False

search_options

Contextual search overrides

TYPE: SearchOptions DEFAULT: None

update_options

Contextual update overrides

TYPE: UpdateOptions DEFAULT: None

insert_options

Contextual insert overrides

TYPE: InsertOptions DEFAULT: None

clause

Contextual sql_clause override

TYPE: SQLClause DEFAULT: None

Source code in src/arcpie/featureclass.py
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
@contextmanager
def options(self,
            *, 
            strict: bool = False,
            search_options: SearchOptions|None=None, 
            update_options: UpdateOptions|None=None, 
            insert_options: InsertOptions|None=None, 
            clause: SQLClause|None=None):
    """Enter a context block where the supplied options replace the stored options for the `Table` or `FeatureClass`

    Args:
        strict (bool): If this is set to `True` the `Table` or `FeatureClass` will not fallback on existing options
            when set to `False`, provided options override existing options (default: `False`)
        search_options (SearchOptions): Contextual search overrides
        update_options (UpdateOptions): Contextual update overrides
        insert_options (InsertOptions): Contextual insert overrides
        clause (SQLClause): Contextual `sql_clause` override
    """
    _src_ops = self.search_options
    _upd_ops = self.update_options
    _ins_ops = self.insert_options
    _clause  = self.clause
    try:
        self._search_options = (
            self._resolve_search_options(_src_ops, search_options or {}) 
            if not strict
            else search_options or SearchOptions()
        )
        self._update_options = (
            self._resolve_update_options(_upd_ops, update_options or {})
            if not strict 
            else insert_options or UpdateOptions()
        )
        self._insert_options = (
            self._resolve_insert_options(_ins_ops, insert_options or {})
            if not strict 
            else insert_options or InsertOptions()
        )
        self._clause = (
            clause or _clause
            if not strict 
            else SQLClause(None, None)
        )
        yield self

    finally:
        self._search_options = _src_ops
        self._update_options = _upd_ops
        self.insert_options = _ins_ops
        self._clause = _clause

row_updater

row_updater(
    *field_names: FieldName,
    strict: bool = False,
    update_options: UpdateOptions | None = None,
    **overrides: Unpack[UpdateOptions],
) -> Generator[_Schema, _Schema | None, None]

A Bi-Directional generator that yields rows and updates them with the sent value

Note

This method will assume the full provided schema if there is one, so make sure you keep track of any applied field filters.

PARAMETER DESCRIPTION

field_names

The fields to include in the update operation (default: All)

TYPE: FieldName | str DEFAULT: ()

strict

Raise a KeyError if an invalid fieldname is passed, otherwise drop invalid updates (default: False)

TYPE: bool DEFAULT: False

update_options

Additional context to pass to the UpdateCursor as a dictionary

TYPE: UpdateOptions DEFAULT: None

**overrides

Additional context to pass to the UpdateCursor as keyword arguments

TYPE: UpdateOptions DEFAULT: {}

Example
>>> updater = fc.row_updater()
>>> for row in updater:
...     if row['Name'] = 'No Name':
...         row['Name'] = None
...         updater.send(row)
Source code in src/arcpie/featureclass.py
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
def row_updater(self, *field_names: FieldName,
                strict: bool=False,
                update_options: UpdateOptions|None=None, 
                **overrides: Unpack[UpdateOptions]) -> Generator[_Schema, _Schema|None, None]:
    """A Bi-Directional generator that yields rows and updates them with the sent value

    Note:
        This method will assume the full provided schema if there is one, so make sure you keep track of
        any applied field filters.

    Args:
        field_names (FieldName|str): The fields to include in the update operation (default: All)
        strict (bool): Raise a KeyError if an invalid fieldname is passed, otherwise drop invalid updates (default: False)
        update_options (UpdateOptions): Additional context to pass to the UpdateCursor as a dictionary
        **overrides (UpdateOptions): Additional context to pass to the UpdateCursor as keyword arguments

    Example:
        ```python
        >>> updater = fc.row_updater()
        >>> for row in updater:
        ...     if row['Name'] = 'No Name':
        ...         row['Name'] = None
        ...         updater.send(row)
        ```
    """
    with self.update_cursor(*(field_names or self.fields), update_options=update_options, **overrides) as cur:
        for row in self.as_dict(cur):
            upd = yield row
            if strict and (invalid := set(upd or []) - set(row)):
                raise KeyError(f'{invalid} fields not found in {self.name}')
            if upd is not None:
                cur.updateRow([upd.get(f, row[f]) for f in cur.fields])

search_cursor

search_cursor(
    *field_names: FieldName,
    search_options: SearchOptions | None = None,
    **overrides: Unpack[SearchOptions],
) -> SearchCursor

Get a SearchCursor for the Table or FeatureClass Supplied search options are resolved by updating the base Table or FeatureClass Search options in this order:

**overrides['kwarg'] -> search_options['kwarg'] -> self.search_options['kwarg']

This is implemented using unpacking operations with the lowest importance option set being unpacked first

{**self.search_options, **(search_options or {}), **overrides}

With direct key word arguments (**overrides) shadowing all other supplied options. This allows a FeatureClass to be initialized using a base set of options, then a shared SearchOptions set to be applied in some contexts, then a direct keyword override to be supplied while never mutating the base options of the FeatureClass.

PARAMETER DESCRIPTION

field_names

The column names to include from the Table or FeatureClass

TYPE: str | Iterable[str] DEFAULT: ()

search_options

A SeachOptions instance that will be used to shadow search_options set on the Table or FeatureClass

TYPE: SearchOptions | None DEFAULT: None

**overrides

Additional keyword arguments for the cursor that shadow both the seach_options variable and the Table or FeatureClass instance SearchOptions

TYPE: Unpack[SeachOptions] DEFAULT: {}

RETURNS DESCRIPTION
SearchCursor

A SearchCursor for the Table or FeatureClass instance that has all supplied options resolved and applied

Example
    >>> cleese_search = SearchOptions(where_clause="NAME = 'John Cleese'")
    >>> idle_search = SearchOptions(where_clause="NAME = 'Eric Idle'")
    >>> monty = Table or FeatureClass('<path>', search_options=cleese_search)
    >>> print(list(monty.search_cursor('NAME')))
    [('John Cleese',)]
    >>> print(list(monty.search_cursor('NAME', search_options=idle_search)))
    [('Eric Idle', )]
    >>> print(list(monty.search_cursor('NAME', search_options=idle_search)), where_clause="NAME = Graham Chapman")
    [('Graham Chapman', )]

In this example, you can see that the keyword override is the most important. The fact that the other searches are created outside initialization allows you to store common queries in one place and update them for all cursors using them at the same time, while still allowing specific instances of a cursor to override those shared/stored defaults.

Source code in src/arcpie/featureclass.py
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
def search_cursor(self, *field_names: FieldName,
                  search_options: SearchOptions|None=None, 
                  **overrides: Unpack[SearchOptions]) -> SearchCursor:
    """Get a `SearchCursor` for the `Table` or `FeatureClass`
    Supplied search options are resolved by updating the base `Table` or `FeatureClass` Search options in this order:

    `**overrides['kwarg'] -> search_options['kwarg'] -> self.search_options['kwarg']`

    This is implemented using unpacking operations with the lowest importance option set being unpacked first

    `{**self.search_options, **(search_options or {}), **overrides}`

    With direct key word arguments (`**overrides`) shadowing all other supplied options. This allows a FeatureClass to
    be initialized using a base set of options, then a shared SearchOptions set to be applied in some contexts,
    then a direct keyword override to be supplied while never mutating the base options of the FeatureClass.

    Args:
        field_names (str | Iterable[str]): The column names to include from the `Table` or `FeatureClass`
        search_options (SearchOptions|None): A `SeachOptions` instance that will be used to shadow
            `search_options` set on the `Table` or `FeatureClass`
        **overrides ( Unpack[SeachOptions] ): Additional keyword arguments for the cursor that shadow 
            both the `seach_options` variable and the `Table` or `FeatureClass` instance `SearchOptions`

    Returns:
        ( SearchCursor ): A `SearchCursor` for the `Table` or `FeatureClass` instance that has all supplied options
            resolved and applied

    Example:
        ```python
            >>> cleese_search = SearchOptions(where_clause="NAME = 'John Cleese'")
            >>> idle_search = SearchOptions(where_clause="NAME = 'Eric Idle'")
            >>> monty = Table or FeatureClass('<path>', search_options=cleese_search)
            >>> print(list(monty.search_cursor('NAME')))
            [('John Cleese',)]
            >>> print(list(monty.search_cursor('NAME', search_options=idle_search)))
            [('Eric Idle', )]
            >>> print(list(monty.search_cursor('NAME', search_options=idle_search)), where_clause="NAME = Graham Chapman")
            [('Graham Chapman', )]
        ```
    In this example, you can see that the keyword override is the most important. The fact that the other searches are
    created outside initialization allows you to store common queries in one place and update them for all cursors using 
    them at the same time, while still allowing specific instances of a cursor to override those shared/stored defaults.
    """
    return SearchCursor(self.path, field_names, **self._resolve_search_options(search_options, overrides))

select

select(
    method: Literal[
        "NEW",
        "DIFFERENCE",
        "INTERSECT",
        "SYMDIFFERENCE",
        "UNION",
    ] = "NEW",
) -> None

If the Table or FeatureClass is bound to a layer, update the layer selection with the active SearchOptions

PARAMETER DESCRIPTION

method

The method to use to apply the selection

DIFFERENCE: Selects the features that are not in the current selection but are in the Table or FeatureClass.

INTERSECT: Selects the features that are in the current selection and the Table or FeatureClass.

NEW: Creates a new feature selection from the Table or FeatureClass.

SYMDIFFERENCE: Selects the features that are in the current selection or the Table or FeatureClass but not both.

UNION: Selects all the features in both the current selection and those in Table or FeatureClass.

TYPE: Literal['NEW', 'DIFFERENCE', 'INTERSECT', 'SYMDIFFERENCE', 'UNION'] DEFAULT: 'NEW'

Note

Selection changes require the project file to be saved to take effect.

Source code in src/arcpie/featureclass.py
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
def select(self, method: Literal['NEW','DIFFERENCE','INTERSECT','SYMDIFFERENCE','UNION']='NEW') -> None:
    """If the Table or FeatureClass is bound to a layer, update the layer selection with the active SearchOptions

    Args:
        method: The method to use to apply the selection\n
            `DIFFERENCE`: Selects the features that are not in the current selection but are in the Table or FeatureClass.\n
            `INTERSECT`: Selects the features that are in the current selection and the Table or FeatureClass.\n
            `NEW`: Creates a new feature selection from the Table or FeatureClass.\n
            `SYMDIFFERENCE`: Selects the features that are in the current selection or the Table or FeatureClass but not both.\n
            `UNION`: Selects all the features in both the current selection and those in Table or FeatureClass.\n

    Note:
        Selection changes require the project file to be saved to take effect. 
    """
    if self.layer:
        _selected = list(self['OID@'])
        self.layer.setSelectionSet(_selected, method=method)
        selected = self.layer.getSelectionSet() or set()
        _query = 'NO QUERY'
        try: # Try to select the layer in the active map
            if len(selected) == 1:
                _query = f'{self.oid_field_name} = {list(selected)[0]}'
            elif len(selected) > 1:
                _query = f'{self.oid_field_name} IN ({format_query_list(selected)})'
            else:
                return
            SelectLayerByAttribute(self.layer.longName, 'NEW_SELECTION', _query)
            return
        except Exception:
            return

unselect

unselect() -> None

If the Table or FeatureClass is bound to a layer, Remove layer selection

Note

Selection changes require the project file to be saved to take effect.

Source code in src/arcpie/featureclass.py
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
def unselect(self) -> None:
    """If the Table or FeatureClass is bound to a layer, Remove layer selection

    Note:
        Selection changes require the project file to be saved to take effect.
    """
    if self.layer:
        self.layer.setSelectionSet(method='NEW')
        try: # Try to unselect the layer in the active map
            SelectLayerByAttribute(self.layer.longName, 'CLEAR_SELECTION')
        except Exception:
            return

update_cursor

update_cursor(
    *field_names: FieldName,
    update_options: UpdateOptions | None = None,
    **overrides: Unpack[UpdateOptions],
) -> UpdateCursor

See Table.search_cursor doc for general info. Operation of this method is identical but returns an UpdateCursor

Source code in src/arcpie/featureclass.py
596
597
598
599
600
601
602
603
604
def update_cursor(self, *field_names: FieldName,
                update_options: UpdateOptions|None=None, 
                **overrides: Unpack[UpdateOptions]) -> UpdateCursor:
    """See `Table.search_cursor` doc for general info. Operation of this method is identical but returns an `UpdateCursor`"""
    if 'datum_transformation' in overrides and overrides['datum_transformation'] is None:
        overrides.pop('datum_transformation')
    if update_options and 'datum_transformation' in update_options and update_options['datum_transformation'] is None:
        update_options.pop('datum_transformation')
    return UpdateCursor(self.path, field_names, **self._resolve_update_options(update_options, overrides))

updater

updater(*fields: FieldName, strict: bool = False)

A wrapper around row_updater that allows use as a context manager

This simplifies the interaction with the row_updater method by allowing inline declaration of the generator. For most simple update operations, this manager should work well.

PARAMETER DESCRIPTION

fields

The fields to include in the update operation (default: All)

TYPE: FieldName | str DEFAULT: ()

stict

Raise a KeyError if an invalid fieldname is passed, otherwise drop invalid updates (default: False)

TYPE: bool

Example

with fc.editor, fc.updater() as upd: ... for row in upd: ... row['Name'] = 'Dave' ... upd.send(row)

Source code in src/arcpie/featureclass.py
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
@contextmanager
def updater(self, *fields: FieldName, strict: bool=False):
    """A wrapper around `row_updater` that allows use as a context manager

    This simplifies the interaction with the `row_updater` method by allowing inline declaration
    of the generator. For most simple update operations, this manager should work well. 

    Args:
        fields (FieldName|str): The fields to include in the update operation (default: All)
        stict (bool): Raise a KeyError if an invalid fieldname is passed, otherwise drop invalid updates (default: False)

    Example:
        >>> with fc.editor, fc.updater() as upd:
        ...     for row in upd:
        ...         row['Name'] = 'Dave'
        ...         upd.send(row)
    """
    try:
        yield self.row_updater(*(fields or self.fields), strict=strict)
    finally:
        pass

where

Apply a where clause to a Table or FeatureClass in a context

PARAMETER DESCRIPTION

where_clause

The where clause to apply to the Table or FeatureClass

TYPE: WhereClause | str

Example
>>> with fc.where("first = 'John'") as f:
...     for f in fc:
...         print(f)
{'first': 'John', 'last': 'Cleese', 'year': 1939}

>>> with fc.where('year > 1939'):
...     print(len(fc))
5
... print(len(fc))
6
Note

This method of filtering a Table or FeatureClass will always be more performant than using the .filter method. If you can achieve the filtering you want with a where clause, do it.

Source code in src/arcpie/featureclass.py
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
@contextmanager
def where(self, where_clause: WhereClause|str):
    """Apply a where clause to a Table or FeatureClass in a context

    Args:
        where_clause (WhereClause|str): The where clause to apply to the Table or FeatureClass

    Example:
        ```python
        >>> with fc.where("first = 'John'") as f:
        ...     for f in fc:
        ...         print(f)
        {'first': 'John', 'last': 'Cleese', 'year': 1939}

        >>> with fc.where('year > 1939'):
        ...     print(len(fc))
        5
        ... print(len(fc))
        6
        ```

    Note:
        This method of filtering a Table or FeatureClass will always be more performant than using the 
        `.filter` method. If you can achieve the filtering you want with a where clause, do it.
    """
    with self.options(
        search_options=SearchOptions(where_clause=str(where_clause)),
        update_options=UpdateOptions(where_clause=str(where_clause))):
        yield self

as_dict

as_dict(
    cursor: SearchCursor | UpdateCursor,
) -> Iterator[RowRecord]

Take a Cusrsor object and yield rows from it

PARAMETER DESCRIPTION

cursor

The cursor to convert to a RowRecord iterator

TYPE: SearchCursor | UpdateCursor

YIELDS DESCRIPTION
RowRecord

Iterator[RowRecord]

Example

```python

for row in as_dict(SearchCursor('table', ['Name', 'City'])) ... print(f'{row["Name"]} lives in {row["City"]}') Dave lives in New York City Robert lives in Kansas City ...

Source code in src/arcpie/featureclass.py
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
def as_dict(cursor: SearchCursor | UpdateCursor) -> Iterator[RowRecord]:
    """Take a Cusrsor object and yield rows from it 

    Args:
        cursor (SearchCursor | UpdateCursor): The cursor to convert to a RowRecord iterator

    Yields:
        Iterator[RowRecord]

    Example:
        ```python
        >>> for row in as_dict(SearchCursor('table', ['Name', 'City']))
        ...     print(f'{row["Name"]} lives in {row["City"]}')
        Dave lives in New York City
        Robert lives in Kansas City
        ...
    """
    yield from (dict(zip(cursor.fields, row)) for row in cursor)

count

Get the record count of a FeatureClass

PARAMETER DESCRIPTION

featureclass

The FeatureClass or Iterator/view to count

TYPE: FeatureClass | Iterator

Example
>>> fc = FeatureClass[PointGeometry]('MyFC')
>>> count(fc)
1000
>>> count(fc[where('1=0')])
0
>>> boundary = next(FeatureClass[Polygon]('Boundaries').shapes)
>>> count(fc[boundary])
325
Source code in src/arcpie/featureclass.py
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
def count(featureclass: FeatureClass | Iterator[Any]) -> int:
    """Get the record count of a FeatureClass

    Args:
        featureclass (FeatureClass | Iterator): The FeatureClass or Iterator/view to count

    Example:
        ```python
        >>> fc = FeatureClass[PointGeometry]('MyFC')
        >>> count(fc)
        1000
        >>> count(fc[where('1=0')])
        0
        >>> boundary = next(FeatureClass[Polygon]('Boundaries').shapes)
        >>> count(fc[boundary])
        325
        ```
    """
    # The __len__() method of FeatureClass only iterates
    # object ID values so this is a small optimisation we can do
    if isinstance(featureclass, FeatureClass):
        return len(featureclass)

    return sum(1 for _ in featureclass)

extract_singleton

extract_singleton(
    vals: Sequence[Any] | Any,
) -> Any | Sequence[Any]

Helper function to allow passing single values to arguments that expect a tuple

PARAMETER DESCRIPTION

vals

The values to normalize based on item count

TYPE: Sequence[Any] | Any

RETURNS DESCRIPTION
Sequence[Any] | Any

The normalized sequence

Source code in src/arcpie/featureclass.py
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
def extract_singleton(vals: Sequence[Any] | Any) -> Any | Sequence[Any]:
    """Helper function to allow passing single values to arguments that expect a tuple

    Args:
        vals (Sequence[Any] | Any): The values to normalize based on item count

    Returns:
        ( Sequence[Any] | Any  ): The normalized sequence
    """
    # String sequences are returned directly
    if isinstance(vals, str):
        return vals

    # Singleton sequences are flattened to the first value
    if len(vals) == 1:
        return vals[0]

    # Default to returning the arg
    return vals

filter_fields

Decorator for filter functions that limits fields checked by the SearchCursor

PARAMETER DESCRIPTION

*fields

Varargs for the fields to limit the filter to

TYPE: FieldName DEFAULT: ()

RETURNS DESCRIPTION
FilterFunc

A filter function with a fields attribute added

Callable[[FilterFunc[RowRecord]], FilterFunc[RowRecord]]

Used with FeatureClass.filter to limit columns

Note

Iterating filtered rows using a decorated filter will limit available columns inside the context of the filter. This should only be used if you need to improve performance of a filter and don't care about the fields not included in the filter_fields decorator:

Example:

>>> @filter_fields('Name', 'Age')
>>> def age_over_21(row):
...     return row['Age'] > 21
...
>>> for row in feature_class[age_over_21]:
...     print(row)
...
{'Name': 'John', 'Age': 23}
{'Name': 'Terry', 'Age': 42}
...
>>> for row in feature_class:
...     print(row)
...
{'Name': 'John', 'LastName': 'Cleese', 'Age': 23}
{'Name': 'Graham', 'LastName': 'Chapman', 'Age': 18}
{'Name': 'Terry', 'LastName': 'Gilliam', 'Age': 42}
...

Note

You can achieve field filtering using the FeatureClass.fields_as context manager as well. This method adds a level of indentation and can be more extensible:

Example:

>>> def age_over_21(row):
...     return row['Age'] > 21
...
>>> with feature_class.fields_as('Name', 'Age'):
...     for row in feature_class[age_over_21]:
...         print(row)
...
{'Name': 'John', 'Age': 23}
{'Name': 'Terry', 'Age': 42}
Since the inspected fields live in the same code block as the filter that uses them, you can easily add the fields in one place. This method is preferred for data manipulation operations while counting operations can use the decorated filter to cut down on boilerplate.

Source code in src/arcpie/featureclass.py
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
def filter_fields(*fields: FieldName) -> Callable[[FilterFunc[RowRecord]], FilterFunc[RowRecord]]:
    """Decorator for filter functions that limits fields checked by the SearchCursor

    Args:
        *fields (FieldName): Varargs for the fields to limit the filter to

    Returns:
        (FilterFunc): A filter function with a `fields` attribute added
        Used with FeatureClass.filter to limit columns

    Note:
        Iterating filtered rows using a decorated filter will limit available columns inside the 
        context of the filter. This should only be used if you need to improve performance of a 
        filter and don't care about the fields not included in the `filter_fields` decorator:

        Example:
            ```python
            >>> @filter_fields('Name', 'Age')
            >>> def age_over_21(row):
            ...     return row['Age'] > 21
            ...
            >>> for row in feature_class[age_over_21]:
            ...     print(row)
            ...
            {'Name': 'John', 'Age': 23}
            {'Name': 'Terry', 'Age': 42}
            ...
            >>> for row in feature_class:
            ...     print(row)
            ...
            {'Name': 'John', 'LastName': 'Cleese', 'Age': 23}
            {'Name': 'Graham', 'LastName': 'Chapman', 'Age': 18}
            {'Name': 'Terry', 'LastName': 'Gilliam', 'Age': 42}
            ...
            ```

    Note:
        You can achieve field filtering using the `FeatureClass.fields_as` context manager as well. 
        This method adds a level of indentation and can be more extensible:

        Example:
            ```python
            >>> def age_over_21(row):
            ...     return row['Age'] > 21
            ...
            >>> with feature_class.fields_as('Name', 'Age'):
            ...     for row in feature_class[age_over_21]:
            ...         print(row)
            ...
            {'Name': 'John', 'Age': 23}
            {'Name': 'Terry', 'Age': 42}
            ```
        Since the inspected fields live in the same code block as the filter that uses them, you can 
        easily add the fields in one place. This method is preferred for data manipulation operations 
        while counting operations can use the decorated filter to cut down on boilerplate.
    """
    def _filter_wrapper(func: FilterFunc):
        setattr(func, 'fields', fields)
        return func
    return _filter_wrapper

format_query_list

format_query_list(vals: Iterable[Any]) -> str

Format a list of values into a SQL list

Source code in src/arcpie/featureclass.py
206
207
208
209
210
def format_query_list(vals: Iterable[Any]) -> str:
    """Format a list of values into a SQL list"""
    if isinstance(vals, (str , int)):
        return f"{vals}"
    return ','.join([f"{val}" for val in vals])

norm

norm(val: Any) -> str

Normalize a value for SQL query (wrap strings in single quotes)

Source code in src/arcpie/featureclass.py
212
213
214
215
216
def norm(val: Any) -> str:
    """Normalize a value for SQL query (wrap strings in single quotes)"""
    if isinstance(val, str):
        return f"'{val}'"
    return val

valid_field

valid_field(fieldname: FieldName) -> bool

Validate a fieldname

Source code in src/arcpie/featureclass.py
301
302
303
304
305
306
307
308
309
310
311
312
313
314
def valid_field(fieldname: FieldName) -> bool:
    """Validate a fieldname"""
    return not (
            # Has characters
            len(fieldname) == 0
            # Is under 160 characters
            or len(fieldname) > 160
            # Doesn't start with a number
            or fieldname[0] in digits 
            # Only has alphanum and underscore
            or not set(fieldname).issubset(ascii_letters + digits + '_')
            # Doesn't have reserved prefix
            or any(fieldname.startswith(reserved) for reserved in ('gdb_', 'sde_', 'delta_'))
        )

where

where(
    *clauses: str, mode: Literal["AND", "OR"] = "AND"
) -> WhereClause

Wrap a string in a WhereClause object to use with indexing

PARAMETER DESCRIPTION

clauses

Varargs of clause string to mark as a clause

TYPE: str DEFAULT: ()

mode

Join statment for multiple clauses (AND/OR) (default: AND)

TYPE: Literal['AND', 'OR'] DEFAULT: 'AND'

RETURNS DESCRIPTION
WhereClause

WhereClause

Example
>>> for row in features[where('SHAPE_LENGTH > 10')]:
...     print(row)
{'OBJECTID': 1, 'SHAPE_LENGTH': 11}
{'OBJECTID': 2, 'SHAPE_LENGTH': 34}
{'OBJECTID': 3, 'SHAPE_LENGTH': 78}
...
Source code in src/arcpie/featureclass.py
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
def where(*clauses: str, mode: Literal['AND', 'OR'] = 'AND') -> WhereClause:
    """Wrap a string in a WhereClause object to use with indexing

    Args:
        clauses: Varargs of clause string to mark as a clause
        mode: Join statment for multiple clauses (AND/OR) (default: `AND`)

    Returns:
        WhereClause

    Example:
        ```python
        >>> for row in features[where('SHAPE_LENGTH > 10')]:
        ...     print(row)
        {'OBJECTID': 1, 'SHAPE_LENGTH': 11}
        {'OBJECTID': 2, 'SHAPE_LENGTH': 34}
        {'OBJECTID': 3, 'SHAPE_LENGTH': 78}
        ...
        ```
    """
    return WhereClause(f' {mode} '.join(filter(lambda c: bool(c), clauses)))