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
2254
2255
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
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
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
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
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
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
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
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
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
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
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
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
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
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
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]

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

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

is_web

Determine if the FeatureClass is pointing at a web FeatureServer

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[GeoType]

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
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
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@'  # noqa: S107
    ) -> 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

is_web property

is_web: bool

Determine if the FeatureClass is pointing at a web FeatureServer

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[GeoType]

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
1232
1233
1234
1235
def __contains__(self, field: str) -> bool:
    """Implementation of contains that checks for a field existing in the `FeatureClass`
    """
    return field.lower() in map(str.lower, 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
1285
1286
1287
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[GeoType]
__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 (inherit shape reference))
>>> 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
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
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 (inherit shape reference))
        >>> 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 | Geometry | Polygon | PointGeometry | Polyline | Multipoint | Multipatch):
            with self.spatial_filter(shape):
                yield from self
        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
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
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
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
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
1277
1278
1279
def __repr__(self) -> str:
    """Provide a constructor string e.g. `Table or FeatureClass[Polygon]('path')`"""
    return f"{type(self).__name__}({self})"

__str__

__str__() -> str

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

Source code in src/arcpie/featureclass.py
1281
1282
1283
def __str__(self) -> str:
    """Return the `Table` or `FeatureClass` path for use with other arcpy methods"""
    return self.path if self.is_web else 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
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
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
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
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
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
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
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: LayerLike) -> 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
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
def bind_to_layer(self, layer: LayerLike) -> 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.elements import Layer as _Layer  # noqa: PLC0415
    # Try to update datasource using updateConnectionProperties
    try:
        _Layer(layer).data_source = str(self)
    except Exception as e:
        raise ValueError(f'Unable to bind {self} to {layer}') from e

clear

clear() -> None

Clear all records from the table

Source code in src/arcpie/featureclass.py
1073
1074
1075
1076
1077
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[GeoType, Schema]

Create a new FeatureClass instance to prevent overriding a shared resource

Source code in src/arcpie/featureclass.py
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
def copy(self) -> FeatureClass[GeoType, Schema]:
    """Create a new FeatureClass instance to prevent overriding a shared resource"""
    return FeatureClass[GeoType, 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
928
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
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
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
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(
            '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
920
921
922
923
924
925
926
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
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
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
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
874
875
876
877
878
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, strict=False)):
                    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
1079
1080
1081
1082
1083
1084
1085
1086
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
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
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)))

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
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
@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
    orig_fields = self.fields
    self._fields = tuple(fields)
    try:
        yield self
    finally:
        self._fields = orig_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
880
881
882
883
884
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
913
914
915
916
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', self.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) -> GeoType | None
footprint(buffer: None) -> GeoType | None
footprint(*, pairwise: bool) -> GeoType | None
footprint() -> GeoType | None
footprint(
    buffer: float | None = None, pairwise: bool = True
) -> GeoType | 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
GeoType | 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
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
def footprint(self, buffer: float | None = None, pairwise: bool = True) -> GeoType | 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: GeoType | Polygon, nxt: GeoType | Polygon) -> GeoType | 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: LayerLike,
    *,
    ignore_selection: bool = False,
    ignore_def_query: bool = False,
) -> FeatureClass[GeoType, Schema]

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
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
@classmethod
def from_layer(cls, layer: LayerLike,
               *,
               ignore_selection: bool = False,
               ignore_def_query: bool = False,) -> FeatureClass[GeoType, Schema]:
    """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
    """
    layer = cast(Layer, getattr(layer, 'elem', layer))
    try:
        source = layer.dataSource
        if source.startswith('http'):
            fc = cls(source)
        else:
            fc = cls(Path(layer.dataSource).resolve())
        fc._layer = layer
    except Exception as e:
        cim_def = layer.getDefinition('V3')
        if not hasattr(cim_def, 'featureTable'):
            raise
        conn: object = cast(object, cim_def.featureTable.dataConnection)  # type: ignore
        f_ds = getattr(conn, 'featureDataset', '')
        f_name = getattr(conn, 'dataset', '')
        db = getattr(conn, 'workspaceConnectionString', '')
        if not f_name or not db.startswith('DATABASE='):
            raise e
        dataSource = Path(db[9:]) / f_ds / f_name
        fc = cls(dataSource)
        fc._layer = layer

    try:
        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
        )
    except (RuntimeError, AttributeError):
        # Layer is technically invalid, but we want to allow
        # creation anyways (user can validate later with `exists`)
        definition_query = None
        selected_ids = None
        selection = 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)

    return fc

from_table classmethod

from_table(
    table: TableLike,
    *,
    ignore_selection: bool = False,
    ignore_def_query: bool = False,
) -> Table[Schema]

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

Source code in src/arcpie/featureclass.py
1594
1595
1596
1597
1598
1599
1600
@classmethod
def from_table(cls, table: TableLike,
               *,
               ignore_selection: bool = False,
               ignore_def_query: bool = False,) -> Table[Schema]:
    """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[GeoType] | _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
2030
2031
2032
2033
2034
2035
2036
2037
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 DESCRIPTION
dict[str, Any]

A mapping of fieldnames to field values for each row

Source code in src/arcpie/featureclass.py
765
766
767
768
769
770
771
772
773
774
775
776
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]
    | Literal['nodoc']
    | None = 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
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
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] | Literal['nodoc'] | None = 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  # noqa: PLC0415
    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
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
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
778
779
780
781
782
783
784
785
786
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
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
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
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 = dict(zip(group_fields, group, strict=False))
        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
968
969
970
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
612
613
614
615
616
617
618
619
620
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 | dict[str, Any],
    ignore_errors: bool = False,
) -> int | None

Insert a single record into the table

PARAMETER DESCRIPTION

record

A dictionary of fieldname -> value records to insert

TYPE: Schema | dict[str, Any]

ignore_errors

Skip malformed records

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
int | None

int | None: OID of the inserted record or None if the record is malformed or empty

RAISES DESCRIPTION
ValueError

ignore_errors is False and a malformed record is encountered

Source code in src/arcpie/featureclass.py
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
def insert_record(self, record: Schema | dict[str, Any], ignore_errors: bool = False) -> int | None:
    """Insert a single record into the table

    Args:
        record: A dictionary of fieldname -> value records to insert
        ignore_errors: Skip malformed records

    Returns:
        int | None: OID of the inserted record or `None` if the record is malformed or empty

    Raises:
        ValueError: `ignore_errors` is `False` and a malformed record is encountered
    """
    if (missing_fields := set(record.keys()).difference(self.fields)) or not record:
        if not record or ignore_errors:
            return None
        else:
            raise ValueError(f'{missing_fields} not in {self.fields}')
    with self.insert_cursor(*record.keys()) as cur:
        try:
            return cur.insertRow(list(record.values()))
        except Exception as e:
            if not ignore_errors:
                raise ValueError(f'Malformed Row: {record}') from e

insert_records

insert_records(
    records: Iterable[Schema | dict[str, Any]],
    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
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
def insert_records(self, records: Iterable[Schema | dict[str, Any]], 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
961
962
963
964
965
966
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
1390
1391
1392
1393
1394
1395
1396
1397
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
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
@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
1928
1929
1930
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
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
@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]

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
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
def row_updater(self, *field_names: FieldName,
                strict: bool = False,
                update_options: UpdateOptions | None = None,
                **overrides: Unpack[UpdateOptions]) -> Generator[Schema, Schema | 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
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
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
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
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} = {next(iter(selected))}'
            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 (features will be projected to match its coordiate system)

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
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
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
@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 (features will be projected to match its coordiate system)
        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,
            spatial_reference=spatial_filter.spatialReference)):
        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
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
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
622
623
624
625
626
627
628
629
630
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
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
@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
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
@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,
)

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

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

is_web

Determine if the FeatureClass is pointing at a web FeatureServer

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 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
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
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

is_web property

is_web: bool

Determine if the FeatureClass is pointing at a web FeatureServer

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
1232
1233
1234
1235
def __contains__(self, field: str) -> bool:
    """Implementation of contains that checks for a field existing in the `FeatureClass`
    """
    return field.lower() in map(str.lower, 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
1285
1286
1287
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
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
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
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
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
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
1190
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
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
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
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
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
1277
1278
1279
def __repr__(self) -> str:
    """Provide a constructor string e.g. `Table or FeatureClass[Polygon]('path')`"""
    return f"{type(self).__name__}({self})"

__str__

__str__() -> str

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

Source code in src/arcpie/featureclass.py
1281
1282
1283
def __str__(self) -> str:
    """Return the `Table` or `FeatureClass` path for use with other arcpy methods"""
    return self.path if self.is_web else 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
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
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
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
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
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
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
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: LayerLike) -> 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
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
def bind_to_layer(self, layer: LayerLike) -> 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.elements import Layer as _Layer  # noqa: PLC0415
    # Try to update datasource using updateConnectionProperties
    try:
        _Layer(layer).data_source = str(self)
    except Exception as e:
        raise ValueError(f'Unable to bind {self} to {layer}') from e

clear

clear() -> None

Clear all records from the table

Source code in src/arcpie/featureclass.py
1073
1074
1075
1076
1077
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
1666
1667
1668
1669
1670
1671
1672
1673
1674
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
928
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
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
920
921
922
923
924
925
926
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
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
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
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
874
875
876
877
878
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, strict=False)):
                    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
1079
1080
1081
1082
1083
1084
1085
1086
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
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
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)))

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
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
@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
    orig_fields = self.fields
    self._fields = tuple(fields)
    try:
        yield self
    finally:
        self._fields = orig_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
880
881
882
883
884
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
913
914
915
916
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', self.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: LayerLike,
    *,
    ignore_selection: bool = False,
    ignore_def_query: bool = False,
) -> Table[Schema]

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
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
@classmethod
def from_layer(cls, layer: LayerLike,
               *,
               ignore_selection: bool = False,
               ignore_def_query: bool = False,) -> Table[Schema]:
    """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
    """
    layer = cast(Layer, getattr(layer, 'elem', layer))
    try:
        source = layer.dataSource
        if source.startswith('http'):
            fc = cls(source)
        else:
            fc = cls(Path(layer.dataSource).resolve())
        fc._layer = layer
    except Exception:
        cim_def = layer.getDefinition('V3')
        if not hasattr(cim_def, 'featureTable'):
            raise
        conn: object = cast(object, cim_def.featureTable.dataConnection)  # type: ignore
        f_ds = getattr(conn, 'featureDataset', '')
        f_name = getattr(conn, 'dataset', '')
        db = getattr(conn, 'workspaceConnectionString', '')
        if not f_name or not db.startswith('DATABASE='):
            raise
        dataSource = Path(db[9:]) / f_ds / f_name
        fc = cls(dataSource)
        fc._layer = layer

    try:
        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
        )
    except (RuntimeError, AttributeError):
        selected_ids = None
        definition_query = None
        selection = 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)

    return fc

from_table classmethod

from_table(
    table: TableLike,
    *,
    ignore_selection: bool = False,
    ignore_def_query: bool = False,
) -> Table[Schema]

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

Source code in src/arcpie/featureclass.py
1594
1595
1596
1597
1598
1599
1600
@classmethod
def from_table(cls, table: TableLike,
               *,
               ignore_selection: bool = False,
               ignore_def_query: bool = False,) -> Table[Schema]:
    """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
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
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 DESCRIPTION
dict[str, Any]

A mapping of fieldnames to field values for each row

Source code in src/arcpie/featureclass.py
765
766
767
768
769
770
771
772
773
774
775
776
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]
    | Literal['nodoc']
    | None = 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
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
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] | Literal['nodoc'] | None = 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  # noqa: PLC0415
    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
778
779
780
781
782
783
784
785
786
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
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
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
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 = dict(zip(group_fields, group, strict=False))
        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
968
969
970
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
612
613
614
615
616
617
618
619
620
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 | dict[str, Any],
    ignore_errors: bool = False,
) -> int | None

Insert a single record into the table

PARAMETER DESCRIPTION

record

A dictionary of fieldname -> value records to insert

TYPE: Schema | dict[str, Any]

ignore_errors

Skip malformed records

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
int | None

int | None: OID of the inserted record or None if the record is malformed or empty

RAISES DESCRIPTION
ValueError

ignore_errors is False and a malformed record is encountered

Source code in src/arcpie/featureclass.py
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
def insert_record(self, record: Schema | dict[str, Any], ignore_errors: bool = False) -> int | None:
    """Insert a single record into the table

    Args:
        record: A dictionary of fieldname -> value records to insert
        ignore_errors: Skip malformed records

    Returns:
        int | None: OID of the inserted record or `None` if the record is malformed or empty

    Raises:
        ValueError: `ignore_errors` is `False` and a malformed record is encountered
    """
    if (missing_fields := set(record.keys()).difference(self.fields)) or not record:
        if not record or ignore_errors:
            return None
        else:
            raise ValueError(f'{missing_fields} not in {self.fields}')
    with self.insert_cursor(*record.keys()) as cur:
        try:
            return cur.insertRow(list(record.values()))
        except Exception as e:
            if not ignore_errors:
                raise ValueError(f'Malformed Row: {record}') from e

insert_records

insert_records(
    records: Iterable[Schema | dict[str, Any]],
    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
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
def insert_records(self, records: Iterable[Schema | dict[str, Any]], 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
961
962
963
964
965
966
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
1390
1391
1392
1393
1394
1395
1396
1397
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
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
@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]

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
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
def row_updater(self, *field_names: FieldName,
                strict: bool = False,
                update_options: UpdateOptions | None = None,
                **overrides: Unpack[UpdateOptions]) -> Generator[Schema, Schema | 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
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
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
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
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} = {next(iter(selected))}'
            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
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
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
622
623
624
625
626
627
628
629
630
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
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
@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
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
@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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
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, strict=False)) for row in cursor)

count

count(
    it: FeatureClass[Any, Any]
    | Iterator[Any]
    | Iterable[Any],
) -> int

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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
def count(it: FeatureClass[Any, Any] | Iterator[Any] | Iterable[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(it, FeatureClass):
        return len(it)  # type: ignore

    return sum(1 for _ in it)

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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
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 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
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
300
301
302
303
304
305
306
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):
        func.fields = fields  # type: ignore (creating this attribute here)
        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
210
211
212
213
214
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
217
218
219
220
221
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
309
310
311
312
313
314
315
316
317
318
319
320
321
322
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
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
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)))