Changelog¶
All notable changes to restgdf are documented here. This project follows Semantic Versioning.
[3.0.0] - 2026-05-02¶
2.0.0 - 2026-05-02¶
Changed¶
Gate-3 hardening follow-up. Three review-driven safety fixes land on top of the v3-followup tranche:
ArcGIS requests routed through
_choose_verbnow forcePOSTwhenever the effective session transport is"body"or"query"(including wrappedResilientSession(ArcGISTokenSession(...))stacks), preventing auth tokens from leaking into URL query strings on short requests.restgdf.resilience._retry._RetriedCtxnow mirrorsaiohttp’s dual request-manager shape soawait session.get(...)andasync with session.get(...)both work againstResilientSession.getgdf._advertised_max_record_count_factor()now rejectsbool,NaN, and infinity inputs so malformed vendor metadata falls back to the byte-identical pre-T9 path.Module-level
get_gdf(..., session=None)now closes the temporaryaiohttp.ClientSessionit creates internally, eliminating the unclosed-session leak on direct helper usage._iter_pages_raw(..., max_concurrent_pages=N)now keeps at mostNfetch tasks scheduled at once instead of pre-creating one task per page and only bounding execution with a semaphore, preventing pagination plans with many batches from exploding task memory.Legacy streaming helpers (
_feature_batch_generator,chunk_generator) now honor the repository-wide concurrency cap while they stream results and cancel outstanding work on early generator close, preventing abandoned page-fetch tasks from accumulating behind partially-consumed iterators.Hypothesis-backed property tests now live behind a dedicated
pytest --run-stressopt-in so the default suite remains a representative production-validation pass instead of mixing in a separate stress tier by default.
Pagination planner wiring. When an ArcGIS layer advertises
advancedQueryCapabilities.maxRecordCountFactor,get_query_data_batchesnow forwards that value tobuild_pagination_plan(advertised_factor=...). The wiring is strictly opt-in: servers that do not expose the field (or expose a non-positive / non-numeric value) get the previous byte-exact plan with noadvertised_factorkwarg. This replaces the deferred-plumbing stub.Feature-count retry delegation.
restgdf.utils.getinfo._feature_count_with_timeoutnow delegates its bounded timeout-retry loop torestgdf.resilience.bounded_retry_timeout(a new public helper) when theresilienceextra is installed, giving restgdf a single stamina-backed source of truth for retry semantics. When the extra is absent, the previous inline loop is preserved byte-for-byte as a fallback. The retryable exception set (asyncio.TimeoutError,TimeoutError,aiohttp.ServerTimeoutError) and R-69 (ClientConnectionErrorpropagates without retry) are preserved on both paths.GET/POST verb selection wiring. ArcGIS query requests now route through a single
_arcgis_requesthelper inrestgdf/utils/_http.pythat consults_choose_verb(8,192-byte threshold on URL + urlencoded body). Previously every call site was hard-codedPOST. Nine call sites acrossutils/getgdf.py,utils/getinfo.py, andutils/_query.pywere migrated. The GET path coercesbool/Nonevalues inparamsto"true"/"false"/""so yarl can serialize them; POST payloads are untouched. Zero behavior change for bodies above the threshold.Transport typing.
ArcGISTokenSessionnow exposesclose()andclosedthat delegate to its inneraiohttp.ClientSession, making it fully satisfy therestgdf._client._protocols.AsyncHTTPSessionProtocol. Internal call sites previously typedaiohttp.ClientSession | ArcGISTokenSessionwere widened toAsyncHTTPSessionacrossadapters/stream.py,directory/directory.py,featurelayer/featurelayer.py,utils/crawl.py,utils/getgdf.py,utils/getinfo.py,utils/_query.py, andutils/_stats.py. Zero runtime behavior change — widening to a superset Protocol is backwards-compatible for existing callers.
Added¶
Pagination¶
restgdf.errors.PaginationInconsistencyWarning— newUserWarningsubclass emitted by_resolve_pagewhen a batch page returns zero features but the server still setsexceededTransferLimit=true. The warning fires regardless of theon_truncationmode ("raise","ignore", or"split") so pathological server responses are always surfaced. Deliberately not included inrestgdf.errors.__all__or the top-level public API — warnings live outside theRestgdfErrortaxonomy; import viafrom restgdf.errors import PaginationInconsistencyWarning.
Domain resolution¶
FeatureLayer.get_df(resolve_domains=False)— new kwarg on the pandas-first tabular accessor. WhenTrue, coded-value domain fields are replaced in-place with their human-readable names using a single cached pass over the layer’s metadata (no per-row HTTP). Defaults toFalseso the base code path is byte-identical for existing callers.restgdf.adapters.pandas.resolve_domains(df, fields)— public helper exposing the same resolution logic for callers already holding apandas.DataFrame. Requires thegeoextra (pandas is part of that install surface).
Resilience¶
restgdf.resilience.bounded_retry_timeout— new public helper exposing a stamina-backed bounded retry loop for timeout-class exceptions. Used internally by_feature_count_with_timeoutwhen theresilienceextra is installed; safe for consumers on the same extra. The retryable exception set matches restgdf’s internal timeout policy (asyncio.TimeoutError,TimeoutError,aiohttp.ServerTimeoutError);aiohttp.ClientConnectionErrorpropagates immediately (R-69 preserved).
Streaming¶
FeatureLayer.iter_pages— low-level async generator yielding raw ArcGIS query-page envelopes withorder("request"default /"completion"),max_concurrent_pages(optional semaphore bound), andon_truncation("raise"default /"ignore"/"split"). The"split"strategy bisects the predicate’s OID list viaget_object_idsand recurses up to depth 32 before raising. Truncated pages under"ignore"log a structured warning on therestgdf.paginationlogger and continue.FeatureLayer.iter_features/FeatureLayer.stream_features— flatteniter_pagesinto individual feature dicts. Deliberate aliases:stream_featuresis the canonical public entrypoint,iter_featuresthe lower-level primitive.FeatureLayer.stream_feature_batches— yields onelist[feature_dict]per page, mirroringiter_pagesboundaries.FeatureLayer.stream_rows— yields row-shaped dicts (attributesmerged with rawgeometry). Pandas/GeoPandas-free; safe on a base install.FeatureLayer.stream_gdf_chunks— yieldsGeoDataFramechunks over the optional geo stack; each chunk inheritsattrs["spatial_reference"].iter_pagesnow emits exactly onefeature_layer.streamINTERNAL parent span wrapping the per-page loop when telemetry is enabled; no restgdf-owned per-page spans are emitted. No-op whenRESTGDF_TELEMETRY_ENABLEDis unset. Constructed insiderestgdf.utils.getgdf._iter_pages_raw.Spatial-reference propagation:
restgdf.utils.getgdf.get_gdf,FeatureLayer.get_gdf,FeatureLayer.sample_gdf,FeatureLayer.head_gdf, andchunk_generator/FeatureLayer.stream_gdf_chunksall stampgdf.attrs["spatial_reference"]with the raw dict from the layer’s metadata envelope (extent.spatialReferencepreferred, top-levelspatialReferencefallback). Normalization usesrestgdf.utils._metadata.normalize_spatial_reference.
Adapters and tabular output¶
restgdf.adapterssubpackage (lazy-loaded via PEP 562): four submodules covering dict / stream / pandas / geopandas shapes. All submodules are base-install safe at import time; pandas and geopandas are required only at call time and raiseOptionalDependencyErrorwhen missing.restgdf.adapters.dict—feature_to_row,features_to_rows, plusas_dict/as_json_dictre-exports.restgdf.adapters.stream—iter_feature_batches,iter_rows,iter_gdf_chunks.restgdf.adapters.pandas—rows_to_dataframe(sync) +arows_to_dataframe(async).restgdf.adapters.geopandas—rows_to_geodataframe+arows_to_geodataframe.
FeatureLayer.get_df()— async pandas-first tabular accessor. Sibling toget_gdf()that returns apandas.DataFramefrom the same row stream and does not require the geo extra.
Configuration¶
restgdf.Config— frozen pydantic 2.x aggregate of seven frozen sub-configs (TransportConfig,TimeoutConfig,RetryConfig,LimiterConfig,ConcurrencyConfig,AuthConfig,TelemetryConfig) plusConfig.from_env(env=None)classmethod. Sub-configs and the aggregate are immutable at both slot and nested-field level.restgdf.get_config()— process-wide cachedConfigaccessor (functools.lru_cache(maxsize=1)).restgdf.reset_config_cache()— clears the cache; cascades bidirectionally with the existingreset_settings_cacheso tests can refresh all configuration with a single call regardless of which accessor they use.Nested env-var surface
RESTGDF_<CATEGORY>_<FIELD>wired throughConfig.from_envfor every sub-config field (RESTGDF_TRANSPORT_USER_AGENT,RESTGDF_TIMEOUT_TOTAL_S,RESTGDF_RETRY_ENABLED,RESTGDF_LIMITER_RATE_PER_HOST,RESTGDF_CONCURRENCY_MAX_CONCURRENT_REQUESTS,RESTGDF_AUTH_TOKEN_URL,RESTGDF_TELEMETRY_LOG_LEVEL, …). Invalid coercions and validator rejections raiseRestgdfResponseErrorwith the underlyingpydantic.ValidationErrorpreserved as__cause__.Settings.max_concurrent_requests: int = 8(field) +RESTGDF_MAX_CONCURRENT_REQUESTSenv-var coercion (BL-01). Default matches aiohttpTCPConnectorpool size.
Errors¶
restgdf.errorsmodule exposing the canonical exception taxonomy:RestgdfError,ConfigurationError,OptionalDependencyError,TransportError,RestgdfTimeoutError,RateLimitError,ArcGISServiceError,PaginationError,FieldDoesNotExistError,SchemaValidationError,AuthenticationError, andOutputConversionError. All re-exported from the top-levelrestgdfpackage via the lazy-import hook.PaginationError.batch_index/.page_sizeattributes carry pagination context when cursor-based iteration fails.RateLimitError.retry_afterattribute carries the optional seconds-until-retry hint, populated from the server’sRetry-Afterheader (integer seconds or RFC 7231 HTTP-date) by the resilience wrapper (Q-A12). New helper_parse_retry_afterinrestgdf.resilience._errors.Error-attribute population:
RestgdfResponseErrornow carries optionalurl,status_code, andrequest_idattributes (kw-only, defaultNone).TransportErrorgainsurlandstatus_code.RestgdfTimeoutErrorgainstimeout_kind("total","connect","read").RateLimitErrorgainsurlandstatus_codealongside existingretry_after. All new attrs are backward-compatible — existing call sites that omit them getNonedefaults.Five
AuthenticationErrorsubclasses —InvalidCredentialsError,TokenExpiredError,TokenRequiredError,TokenRefreshFailedError,AuthNotAttachedError. All carry.context,.attempt,.causeattributes withSecretStrauto-redaction.
Auth runtime¶
TokenSessionConfig.refresh_leeway_seconds(default60) +TokenSessionConfig.clock_skew_seconds(default30) — explicit integer fields (ge=0) replacing the implicit semantics of the previous singlerefresh_threshold_secondsknob (BL-04).ArcGISTokenSession.expires_at— tz-aware UTCdatetimeproperty computed from the epoch-msexpiresfield._utc_now()shim for deterministic wall-clock test control.Structured
auth.refresh.start/.success/.failurelog events at DEBUG level on therestgdf.authlogger.Bounded
/generateTokenretry — transient errors retried up to 3× with exponential backoff; deterministic errors propagate immediately. After exhaustion raisesTokenRefreshFailedError.Referer binding —
token_request_payloadhonoursconfig.refererand switches ArcGISclientto"referer"when set.
Normalization¶
restgdf._models.responses.NormalizedGeometry,NormalizedFeature, anditer_normalized_features(response, *, oid_field=None, sr=None)— typed intermediate models plus iterator overFeaturesResponse.features. Wire-levelfeatures: list[dict]stays for perf; normalization is opt-in. Geometrytypeis heuristically inferred from shape;object_idisint-coerced fromattributes[oid_field].restgdf._models.responses.AdvancedQueryCapabilities— typedPermissiveModelcompanion for the ArcGISadvancedQueryCapabilitiessub-object, with camelCase / snake_caseAliasChoiceswiring and permissiveextra="allow"preservation of unknown keys (BL-21).LayerMetadata.advanced_query_capabilities_typed: AdvancedQueryCapabilities | None— additive typed companion to the existing rawadvanced_query_capabilities: dict | Nonefield. Caller-opt-in; the raw dict stays the default representation.restgdf.utils._metadata.normalize_spatial_reference(sr)— pure helper returning(epsg_int | None, raw_dict | None)that preferslatestWkidoverwkidfor EPSG-consuming clients (R-28).concat_gdfspropagatesGeoDataFrame.attrs["spatial_reference"]across concatenation.restgdf.utils._metadata.normalize_date_fields(features, fields)— converts ArcGISesriFieldTypeDateepoch-ms integers to ISO-8601 UTC strings. Opt-in vianormalize_dates=Trueon the adapter layer.
Pagination¶
restgdf.utils._pagination.PaginationPlan(frozen dataclass) +build_pagination_plan(total_records, max_record_count, *, factor=1.0, advertised_factor=None)— pure-math pagination planner re-exported viarestgdf.utils.getinfo. Emits(resultOffset, resultRecordCount)tuples byte-identical to the previous inline arithmetic inget_query_data_batches; clampsfactor > advertised_factorwith a warning viaget_logger("pagination").get_query_data_batchesis rerouted through the planner with no public-signature change and all pinned fixtures preserved.
Observability¶
restgdf._logging.get_logger(suffix)library-wide logger factory andbuild_log_extrastandardextra=envelope helper. Existingget_drift_logger/restgdf.schema_driftcontract unchanged.restgdf[telemetry]optional extra —RestgdfInstrumentor(dynamic subclass ofAioHttpClientInstrumentor, R-58),feature_layer_stream_spanasync context manager (INTERNAL span, R-21),span_context_fieldshelper, and_SpanContextFilterauto-attached to therestgdfroot logger for trace/span log correlation.docs/recipes/tracing.md— structured observability, error-attribute inspection, and OpenTelemetry integration.docs/recipes/streaming.md— the three streaming shapes,on_truncationoptions,ordervariants, andmax_concurrent_pagesknob.
Resilience¶
restgdf.ResilienceConfig— frozen pydantic sub-config controlling the stamina-based retry wrapper and per-service-root token-bucket rate limiter. Fields:enabled,rate_per_service_root_per_second,respect_retry_after_max_s,fallback_retry_after_seconds,backend. Exposed viarestgdf.Config.resilienceand in top-level__all__. Disabled by default; opt in viaRESTGDF_RESILIENCE_ENABLED=1.restgdf.resilience.ResilientSession— retry + rate-limit adapter implementing theAsyncHTTPSessionprotocol. Stamina-based retry with 429/5xx awareness. Pure pass-through whenResilienceConfig.enabled=False. Requirespip install restgdf[resilience].[project.optional-dependencies] resilienceextra:stamina>=24.2,aiolimiter>=1.1.Per-service-root token-bucket rate limiting via
LimiterRegistryand 429-cooldown viaCooldownRegistryinrestgdf.resilience._limiter._service_root(url)derives the rate-limit key by truncating at the firstFeatureServer/MapServer/ImageServer/SceneServerpath segment.
Transport protocols and drift¶
restgdf._client._protocols.AsyncHTTPSession—@runtime_checkabletyping.Protocolcapturing theget/post/close/closedsurface restgdf transport sessions rely on; re-exported fromrestgdf._client.restgdf._models._drift.FieldSetDriftObserver— observer class that tracks attribute-key appearance / disappearance across feature-page batches and emits dedupedfield_appeared/field_disappearedrecords through the existingrestgdf.schema_driftlogger.Private
restgdf.utils._http._choose_verb(url, body=None)seam returning"POST"for/queryand/queryRelatedRecords,"GET"for bare service/layer metadata URLs, and"POST"as the conservative default. Call sites unchanged; forward-compatible stub for BL-50’s future ~1800-byte GET→POST auto-switch.
Internal helpers¶
restgdf.utils._concurrency.bounded_gather(*aws, semaphore)— caps concurrent fan-out via anasyncio.BoundedSemaphorewhile preservingasyncio.gatherresult ordering andreturn_exceptionssemantics (BL-01).restgdf.utils.getinfo._feature_count_with_timeout— inline bounded retry aroundget_feature_countwith exponential backoff. Retries only onasyncio.TimeoutError,TimeoutError, andaiohttp.ServerTimeoutError; connection-level failures (aiohttp.ClientConnectionError) and deterministic errors (RestgdfResponseError, schema mismatches) propagate on the first attempt. Exhausted timeouts raiseRestgdfTimeoutErrorwith__cause__preserved (BL-51).Directory.safe_crawlnow routes its per-layerfeature_countprobe through aBoundedSemaphoresized fromConcurrencyConfig.max_concurrent_requests(Q-A7).restgdf.__getattr__now consults a_REMOVED_EXPORTSextension point before raisingAttributeError, letting future phases register removed top-level names with aDeprecationWarning+ migration message. Mapping empty in this release (BL-57).
Tests + tooling¶
Taxonomy + observability contract tests (
tests/test_taxonomy_contract.py) assertingerrors.__all__shape and thatget_logger(suffix)emits a structured record for everyLOGGER_SUFFIXESentry (BL-37).Minimal-install contract test (
tests/test_minimal_install.py) guarding against accidental import ofpandas/geopandas/pyogriowhen users install the base package without extras (BL-38).Streaming-recipe discoverability regression test (
tests/test_streaming_recipe_discoverable.py).hypothesis,aioresponses, andopentelemetry-sdkadded to thedevextra. Newtests/test_crawl_property_hypothesis.pyscaffold andtests/_mocks/aioresponses_helpers.pyshared fixtures (BL-39, R-62 scope).bumpverpre_commit_hook(scripts/bumpver_stamp_date.py) auto-stampsCITATION.cff::date-releasedto the release date on everybumpver update, keeping the citation metadata in lock-step withversion:. Newtest_citation_cff_date_released_is_iso_8601pins the ISO-8601 date format (BL-40 follow-up).Install-combination CI matrix (R-62, v3-followup T5). New
install_combinationsjob in.github/workflows/pytest.ymlruns the test suite against six explicit pip install surfaces: base,[geo],[resilience],[telemetry],[geo,resilience,telemetry], and[dev]. Wired into theciaggregator so regressions in any single extra fail PR checks before merge.Coverage-recovery tests (v3-followup T1–T4). Four targeted test modules lift measured coverage from 96.53% to 98.16%:
tests/test_resilience_retry_coverage.py(17 tests;_retry.py79% → 99%),tests/test_telemetry_coverage.py(9 tests;_correlation.py100%,_spans.py97%),tests/test_credentials_coverage.py(6 tests;credentials.py91% → 100%), andtests/test_getgdf_coverage.py(10 tests;getgdf.py95% → 99%). Coverage floor inpyproject.toml([tool.coverage.report] fail_under) raised from 96 to 97 to match (v3-followup T11).
Changed¶
Breaking¶
Default token wire transport flipped from
"body"to"header"(BL-13). Tokens are now sent via theX-Esri-Authorizationheader. Settransport="body"inAuthConfig/TokenSessionConfigto restore the old behavior.refresh_leeway_secondsdefault raised 60 → 120 (BL-13).getgdf/_get_sub_featuresnow raiserestgdf.errors.PaginationError(notRuntimeError) onexceededTransferLimit=true.PaginationErrorcarriesbatch_indexandpage_size(BL-08).PaginationErrorno longer multi-inheritsRuntimeError(phase-3d consolidation under the BL-06 taxonomy). Callers catchingRuntimeErroraroundfeature_count/ pagination calls must widen toRestgdfErroror narrow toPaginationError/ArcGISServiceError(BL-09, R-02).
Non-breaking¶
FeatureLayer.where(new_where)now reuses the parent’s cached metadata so no second metadata GET (?f=json) is issued when the parent was already prepped viaprep()/from_url(). A single feature-count POST (returnCountOnly=true) scoped to the refinedwhereclause is still issued sorefined.countremains correct for the refined filter. The newwhere_clauseis threaded throughdata["where"]so subsequent query / streaming calls honour it (BL-46).ArcGISTokenSession.token_needs_updaterefactored to useexpires_atand_utc_now()instead of inline epoch arithmetic (BL-16).Reactive 498/499 handling in
_call_with_auth_retry— HTTP 498 triggers single-flight refresh + one retry; HTTP 499 raisesAuthNotAttachedErrorimmediately (BL-11).restgdf.utils.getinfo.service_metadata,restgdf.utils.crawl.fetch_all_data, andrestgdf.utils.crawl.safe_crawlnow route their internalasyncio.gatherfan-out throughbounded_gatherwith a per-callasyncio.BoundedSemaphore. Saturation semantics = wait (no new exception) (BL-01).ArcGISTokenSession.update_token_if_needednow collapses concurrent refresh attempts onto a single/generateTokenPOST via a lazily-initialized per-instanceasyncio.Lockwith a double-checkedtoken_needs_update()inside the lock. The new_refresh_lockfield isinit=False,repr=False,compare=False(BL-03).RestgdfResponseErrornow inherits fromrestgdf.errors.RestgdfErrorin addition toValueError. Class identity and thefrom restgdf._models._errors import RestgdfResponseErrorimport path are preserved;except ValueError:call sites keep working (BL-06).restgdf.utils._optional._optional_dependency_errornow returnsrestgdf.errors.OptionalDependencyErrorinstead of a bareModuleNotFoundError. Existingexcept ModuleNotFoundError:andexcept ImportError:handlers still catch the new exception becauseOptionalDependencyErrormulti-inheritsModuleNotFoundError(BL-07).HTTP timeouts are now plumbed through
Settings.timeout_secondsinto every library-maintainedsession.get/session.postcall site (restgdf.utils._query,restgdf.utils._stats,restgdf.utils.getgdf._get_sub_features/get_sub_gdf,ArcGISTokenSession.update_token, and theArcGISTokenSession.get/.postwrappers). The newrestgdf.utils._http.default_timeout()helper returns anaiohttp.ClientTimeoutsized fromSettings.timeout_seconds(float, default30.0, overridable viaRESTGDF_TIMEOUT_SECONDS). Callers that already passtimeout=keep precedence (BL-02).ArcGISTokenSession.__post_init__now respects a caller-suppliedconfig=TokenSessionConfig(...)instead of overwriting it, and derives theTokenSessionConfigsplit fields fromtoken_refresh_thresholdinternally (no longer via the deprecatedrefresh_threshold_secondsalias), so plain construction no longer fires aDeprecationWarning.token_refresh_thresholdis resynced from the validated config after construction.pyproject.toml::[tool.coverage.report].exclude_alsoextended withif TYPE_CHECKING:,@overload, and bare-ellipsis stub lines (standard coverage.py idioms, pydantic / httpx / attrs precedent). Thresholdfail_under=97unchanged (R-63).
Deprecated¶
FeatureLayer.row_dict_generator— useFeatureLayer.stream_rows. EmitsDeprecationWarningand continues to delegate to the module-levelrow_dict_generatorhelper for backwards compatibility with existingunittest.mock.patchcall sites.get_token()— emitsDeprecationWarningon every call (BL-14). Migrate toArcGISTokenSessionfor async token management.get_tokennow also acceptspydantic.SecretStrpasswords.restgdf.Settings/restgdf.get_settings()— userestgdf.Config/restgdf.get_config().get_settings()emits a singleDeprecationWarningon first call and constructs its return value fromget_config(); existing callers continue to work unchanged. Will be removed no earlier than restgdf 3.0 (BL-18).Six flat environment variables —
RESTGDF_TIMEOUT_SECONDS,RESTGDF_TOKEN_URL,RESTGDF_REFRESH_THRESHOLD,RESTGDF_USER_AGENT,RESTGDF_LOG_LEVEL,RESTGDF_MAX_CONCURRENT_REQUESTS— in favour of theirRESTGDF_<CATEGORY>_<FIELD>replacements. The old names continue to work but emit aDeprecationWarningwhen read viaConfig.from_env/get_config; when both old and new names are set the new one wins and the warning notes the override (BL-18).TokenSessionConfig.refresh_threshold_seconds— a read/write alias emittingDeprecationWarning. Reads returnrefresh_leeway_seconds + clock_skew_seconds; constructor writes split the supplied total intoclock_skew_seconds = min(30, total)andrefresh_leeway_seconds = total - clock_skew_seconds. Migrate to the explicit field pair.
Removed¶
restgdf.utils._metadata.FIELDDOESNOTEXISTsentinel (and its re-export viarestgdf.utils.getinfo). Call sites must nowexcept FieldDoesNotExistErrorfrom the BL-06 taxonomy. Hard break — no compat shim (BL-09, R-02).
Fixed¶
bumpverfile pattern forCITATION.cffanchored to^version: {version}$(was unanchoredversion: {version}), preventing a latent release-time defect where the regex would silently rewritecff-version: 1.2.0to the new release version on everybumpver update. Discovered viabumpver update --dryduring the CITATION auto-stamp work.ArcGISTokenSession.update_tokennow forwards the session’sverify_sslflag asssl=on the/generateTokenPOST. Previously the flag was honoured for feature / query requests but ignored during token refresh, soverify_ssl=Falsesessions could still fail TLS verification against self-signed ArcGIS Enterprise deployments.
2.0.0 - 2026-04-20¶
Major release — pydantic 2.13 integration. See
MIGRATION.md for a complete breaking-changes table and
migration recipes.
Breaking¶
Public return and attribute shapes changed from plain
dict/TypedDictto pydanticBaseModelclasses:FeatureLayer.metadata→LayerMetadataDirectory.metadata→LayerMetadataDirectory.services,Directory.services_with_feature_count, andDirectory.crawl(...)→list[CrawlServiceEntry]get_metadata(...)→LayerMetadatasafe_crawl(...)→CrawlReport
AGOLUserPass.passwordis nowpydantic.SecretStr; call.get_secret_value()at the HTTP-POST boundary.restgdf._types.*TypedDicts are replaced by lazy aliases that re-export the new pydantic models and emitDeprecationWarningon import. The shim will be removed in 3.x.
Added¶
LayerMetadata,ServiceInfo,FieldSpec,Feature,FeaturesResponse,CountResponse,ObjectIdsResponse,TokenResponse,ErrorInfo,ErrorResponse,CrawlReport,CrawlServiceEntry,CrawlError— pydantic response models.AGOLUserPass,TokenSessionConfig— pydantic credentials / session config models.Settings,get_settings— process-wide runtime configuration backed byRESTGDF_*environment variables (CHUNK_SIZE,TIMEOUT_SECONDS,USER_AGENT,LOG_LEVEL,TOKEN_URL,REFRESH_THRESHOLD,DEFAULT_HEADERS_JSON).RestgdfResponseError— typed error raised when a strict-tier response fails validation; carriesmodel_name,context, andrawpayload attributes.restgdf.compat.as_dict/restgdf.compat.as_json_dict— migration helpers that convert any returned model (or passthrough any non-model) to a plain dict.restgdf.schema_driftlogger — opt-in observability for vendor variance;NullHandlerby default.Directory.report— the fullCrawlReport(services, errors, root metadata) from the most recent.crawl()call.
Dependencies¶
Added
pydantic>=2.13.3,<3.
1.x¶
Earlier releases were not formally tracked here. See the Git tag history and PyPI release notes for pre-2.0 changes.