Plugin preview. These packages only work with the
pluginsbranch of earthaccess (earthaccess-dev/earthaccess@plugins), which will be released in v2.0.0. See Installing and testing the drafts.
The contract that every earthaccess
plugin follows, plus a cookiecutter template and a conformance suite.
Plugins register a class under the earthaccess.plugins entry-point group;
installing the package is all it takes:
[project.entry-points."earthaccess.plugins"]
myplugin = "earthaccess_myplugin:MyPlugin"Discovery reads distribution metadata only — it never imports a backend. A plugin module is imported the first time it is used:
ea.list_plugins()— the installed plugins (metadata only).ea.list_plugins(details=True)— loads each plugin module (not its backend) to report contract version, auth, output and needs.ea.plugins.myplugin— loadsearthaccess_myplugin, checks thecontract_version, and instantiatesMyPluginwith aPluginContext.
Because of this, import your backend inside methods, never at module level,
so discovery and list_plugins() stay cheap.
Not on PyPI yet. Install the contract and the plugins from GitHub — see Installing and testing the drafts.
Subclass PluginBase (always), declare Options, and implement _request():
from dataclasses import dataclass
from earthaccess_plugin_spec import Asset, PluginBase
class MyPlugin(PluginBase):
name = "myplugin"
needs = frozenset({"concept_id"})
auth = "edl" # 'edl' | 'own' | 'none'
output = "assets" # 'assets' opens/downloads; anything else is passed through
@dataclass
class Options:
format: str = "netcdf4"
def _request(self, results, options):
# import your backend inside the method, never at module level
return self.job(lambda: [Asset(href="s3://...", access="s3")])request(results, **options) validates the inputs, runs _supports(), calls
_request(), and returns a Job immediately. Callers drive every job the same
way:
job = ea.plugins.myplugin.request(results, ...)
job.status() # PENDING | RUNNING | PAUSED | SUCCEEDED | PARTIAL | FAILED | CANCELED
job.info() # backend detail (a dict); never the request payload
job.results() # blocks until settled, then returns the output (or raises)
job.wait(timeout=60) # wait without fetching; returns the final status
repr(job) # never makes a network callThere are three shapes to choose from:
| Shape | How | Example |
|---|---|---|
| A value in hand | return self.job(fn) where fn returns at once |
PyDAP builds OPeNDAP URLs |
| Local compute | return self.job(fn) where fn does the work |
SlideRule processing |
| Remote, polled | subclass Job, override _poll / _fetch / _cancel |
Harmony |
self.job(fn) runs fn in a background thread, so a value in hand is just a
function that returns immediately and a local compute job is one that takes a
while. Only a remote backend needs a Job subclass.
output tells earthaccess how to treat results():
output |
results() returns |
ea.open(job) / ea.download(job, path) |
|---|---|---|
assets |
list[Asset] (files) |
opened / downloaded by earthaccess |
xarray, dataframe, ... |
whatever the plugin deems correct | ea.open returns it as-is; download is not supported |
If your job yields assets, users can open or download it. For anything else,
results() returns whatever makes sense for your backend — a lazy
xarray.Dataset, a GeoDataFrame, native PyDAP datasets, OPeNDAP URLs, ... —
and earthaccess hands it straight back.
needsis checked, not fetched:search_data()already returns full UMM.- Read granule fields through the
earthaccess_plugin_spec.granuleshelpers (peek,collection_label,concept_id,native_id,related_urls, ...) instead of walkingmeta/ummby hand; they never materialize a lazy set. - An advertised capability must be requestable:
subset_variablemeans yourOptionsacceptsvariables, and the conformance suite exercises it. Report formats throughformat_name()so they stay in the sharedFORMATSvocabulary. request()returns aJob;results()blocks and returns the output.- Import your backend inside methods, never at module level.
- Subclass
Jobonly for a remote backend. - Override
_supports()(returns aCapabilities) to report what a collection supports; a definiteFalsemakesrequest()raiseUnsupportedCollectionError. Users can bypass it withcheck=False, ask withplugin.capabilities(results)/plugin.capabilities("ATL06"), or list every plugin's answer withresults.list_services().
- Installing and testing the drafts — install the contract and plugins from GitHub (pre-release).
- The full contract reference —
PluginBasefields,Jobhooks,JobStatus, capabilities, the lazyResultSet, errors, and the conformance checks. - Plugin system overview
- Per-plugin guides: harmony · pydap · sliderule
from earthaccess_plugin_spec.testing import Fixtures, run_conformance
class MyFixtures(Fixtures):
def backend(self):
return patch_my_backend() # a context manager
def test_conformance():
run_conformance(MyPlugin, fixtures=MyFixtures(options={"format": "zarr"}))run_conformance fails with a list of every check that did not pass.
Scaffold a new plugin repository:
cookiecutter gh:earthaccess-dev/earthaccess-plugin-spec --directory templateYou will be asked for the plugin name, backend package, output kind and auth
mode. The generated project has a working PluginBase subclass, an optional
Job subclass (remote only), a conformance test, and CI.
The distribution is earthaccess-plugin-spec; the import package is
earthaccess_plugin_spec.
MIT.