- Home
- Documentation
- API reference
- Archive
- hyperproc.archive.dlr
hyperproc.archive.dlr¶
Source-derived reference
Generated from the current hyperproc 0.1.2 checkout.
Implementation: hyperproc/archive/dlr.py. Signatures, defaults, docstrings, and expandable source are extracted statically; the module is not imported or executed. Names beginning with _ are implementation details, not a stable public API.
Use the function signature as the authority for individual parameter defaults and return annotations. Original docstrings sometimes group parameter names or wrap return descriptions across lines; these descriptions are preserved rather than inferred or rewritten.
The DLR-backed part of :mod:hyperproc.archive: EnMAP and DESIS, over STAC.
EnMAP and DESIS are not in NASA's CMR. DLR's Earth Observation Center runs its own STAC catalogue at https://geoservice.dlr.de/, and it is fully open to search - 238,494 EnMAP L2A scenes and 14,958 DESIS L2A scenes, each with a footprint polygon, a cloud fraction and a per-file asset list::
>>> hits = hp.search("ENMAP", "L2A", bbox=(10, 47, 11.5, 48.5),
... date=("2023-06-01", "2023-09-30"), cloud=(0, 10))
>>> hp.download(hits[:1], "data/")
Downloading is not open: the file server redirects to DLR's CAS single sign-on,
and takes no HTTP Basic auth, so :func:download carries the login form
through once per mission and keeps the session cookie. Access is granted per
mission - EnMAP at https://www.enmap.org/data_access/, DESIS through EOWEB at
https://eoweb.dlr.de/egp/ - so the two accounts need not be the same; see
:func:credentials.
DLR publishes the rasters as cloud-optimised GeoTIFFs, whose names carry an
extra _COG before the extension. hyperproc's EnMAP reader accepts both
spellings, so a downloaded granule opens with no renaming.
BASE = os.environ.get('DLR_STAC_URL', 'https://geoservice.dlr.de/eoc/ogc/stac/v1')
module-attribute
¶
RETRIES = 5
module-attribute
¶
BACKOFF_S = 5.0
module-attribute
¶
SIGNUP = {'ENMAP': 'an EnMAP Access Service account (https://www.enmap.org/data_access/); an EO-Lab account also opens it', 'DESIS': 'an EOC Geoservice account - free self-registration at https://sso.eoc.dlr.de/geoservice/selfservice/register - or an EOWEB DESIS Science account (https://eoweb.dlr.de/egp/)'}
module-attribute
¶
SSO = 'https://sso.eoc.dlr.de/'
module-attribute
¶
NEEDS_LOGIN = needs_login()
module-attribute
¶
PAGE = 500
module-attribute
¶
_FRACTION = re.compile('\\.(\\d+)')
module-attribute
¶
READER_FILES = ('*-METADATA.*', '*-SPECTRAL_IMAGE*.TIF', '*-QL_QUALITY*.TIF', '*-QL_PIXELMASK*.TIF')
module-attribute
¶
POLICY_WORDS = ('usage policy', 'terms of use', 'licence', 'license', 'agreement')
module-attribute
¶
_Forms
¶
Bases: HTMLParser
Every form on a sign-on page, kept apart.
Keeping them apart is the whole point. DLR's EnMAP sign-on carries two: the
EOC username/password form, and a hidden one whose only job is to hand you
off to the EO-Lab identity provider. Merging their fields - as an earlier
version did - meant posting the delegation form's _eventId along with
the credentials, so the sign-on obligingly redirected to EO-Lab's Keycloak
and the EOC password was never tried. DESIS's page has one form, which is
why it worked there and not here.
Source code in hyperproc/archive/dlr.py
forms: list[dict] = []
instance-attribute
¶
headings: list[str] = []
instance-attribute
¶
_cur: dict | None = None
instance-attribute
¶
_in_heading = False
instance-attribute
¶
login: dict | None
property
¶
The form that actually asks for a password.
step: dict | None
property
¶
A form to carry through that is not a login - a consent, a hop.
fields: dict
property
¶
Every field on the page, for messages only - never for posting.
__init__()
¶
handle_starttag(tag, attrs)
¶
Source code in hyperproc/archive/dlr.py
handle_endtag(tag)
¶
needs_login(sensor: str | None = None) -> str
¶
What to do about a refused download, naming the right front door.
Source code in hyperproc/archive/dlr.py
_requests()
¶
credentials(user: str | None = None, password: str | None = None, sensor: str | None = None)
¶
(user, password) from the arguments, the environment or ~/.netrc.
EnMAP and DESIS are served from one host behind one sign-on, but access is
granted per mission through two different portals, so the accounts can
differ. ENMAP_USERNAME/ENMAP_PASSWORD and
DESIS_USERNAME/DESIS_PASSWORD win over the shared
DLR_EOC_* pair; ~/.netrc is the last resort and holds only one,
since both missions share a host.
Returns None when there are none, so a caller can raise with an address
rather than sending an empty login.
Source code in hyperproc/archive/dlr.py
_page_says(html: str) -> str
¶
The sign-on's own message, so a refusal states its actual reason.
"Authentication attempt has failed" and "Your account is locked" arrive with the same HTTP status, and only one of them means the password was wrong.
Source code in hyperproc/archive/dlr.py
_post_to(page_url: str, action: str | None) -> str
¶
Where a form submits to.
An empty or self-referential action posts back to the page including
its query string, which is where CAS keeps the service it is signing
you in to. urljoin would drop it.
Source code in hyperproc/archive/dlr.py
sign_in(session, url: str, auth: tuple[str, str], sensor: str = '', accept_policy: bool = False) -> None
¶
Sign session in to DLR, so it can fetch url and its neighbours.
The file server does not take HTTP Basic auth. It answers 403 to an
Authorization header - with no WWW-Authenticate challenge, which is
how you can tell - and redirects everything else to DLR's CAS single
sign-on. So signing in is the form: fetch it, post the credentials with its
one-time execution token, and come back holding a service ticket. The
session keeps the cookie afterwards, so this happens once however many
files follow.
| PARAMETER | DESCRIPTION |
|---|---|
accept_policy
|
DLR shows an Acceptable Usage Policy once per account
and will not issue a ticket until it is agreed to. That agreement
is yours to give, so by default this stops and says so rather
than clicking it for you. Accept it once in a browser, or pass
TYPE:
|
| RAISES | DESCRIPTION |
|---|---|
PermissionError
|
if the sign-on refuses, quoting the mission's own registration address, or if it is waiting on a policy you have not agreed to. |
Source code in hyperproc/archive/dlr.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 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 | |
read_policy(sensor: str, user: str | None = None, password: str | None = None, width: int = 88) -> str
¶
The policy DLR is waiting on, as readable text.
:func:sign_in refuses to agree to an Acceptable Usage Policy on your
behalf, which is only reasonable if you can read the thing. This signs in,
stops at the policy page, and returns what it says - useful on a server
with no browser.
| PARAMETER | DESCRIPTION |
|---|---|
sensor
|
TYPE:
|
user, password
|
as :func:
|
width
|
wrap column.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
str
|
the policy text, or a line saying there is no policy pending. |
Source code in hyperproc/archive/dlr.py
_visible_text(html: str) -> str
¶
The words of a page, with script, style and markup taken out.
Source code in hyperproc/archive/dlr.py
_rfc3339(date) -> str | None
¶
date= as a STAC datetime interval.
A bare day means the whole day, not midnight, which is the difference between finding a scene and not.
Source code in hyperproc/archive/dlr.py
_time_of(props: dict) -> datetime | None
¶
The acquisition time as naive UTC.
STAC writes fractional seconds with however many digits it has;
fromisoformat wants three or six, so they are padded or trimmed first.
Source code in hyperproc/archive/dlr.py
_links(item: dict, assets: str) -> list[str]
¶
The asset URLs to fetch, in the order the readers want them.
Source code in hyperproc/archive/dlr.py
search(sensor: str, level: str | None = None, *, bbox=None, date=None, cloud: tuple[float, float] | None = None, count: int = 100, assets: str = 'reader', verbose: bool = True, **kwargs) -> Results
¶
Find EnMAP or DESIS scenes in DLR's STAC catalogue.
| PARAMETER | DESCRIPTION |
|---|---|
sensor
|
TYPE:
|
level
|
EnMAP
TYPE:
|
bbox
|
DEFAULT:
|
date
|
DEFAULT:
|
cloud
|
TYPE:
|
count
|
cap on scenes returned.
TYPE:
|
assets
|
which files each result links to.
TYPE:
|
verbose
|
print the query and the total.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Results
|
class: |
Results
|
totals read "size not published" rather than guessing. |
Source code in hyperproc/archive/dlr.py
453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 | |
_granule(item: dict, sensor: str, level: str, collection: str, assets: str = 'reader') -> Granule
¶
Source code in hyperproc/archive/dlr.py
_content_range(r) -> tuple[int | None, int | None]
¶
(first byte, total size) from a 206 or 416 answer, None where absent.
Source code in hyperproc/archive/dlr.py
_retryable(exc: BaseException) -> bool
¶
A dropped or stalled connection, or the server failing on its own side.
Source code in hyperproc/archive/dlr.py
_stream(session, url: str, dest: Path, tmp: Path, key: str, seen: dict | None = None) -> int
¶
Write url into tmp, carrying on from whatever tmp already holds.
The rest is asked for with a Range header. A server that honours it answers 206 and the bytes are appended; one that ignores it answers 200 with the whole file, which then overwrites the partial one - appending a second full copy would make a file that looks finished and is not.
Compression is refused (Accept-Encoding: identity) because byte ranges
and the announced size count the bytes on the wire, and a decoded stream
would match neither.
seen["appending"] is set before the first byte is written, so a caller
whose transfer then breaks can tell bytes added from a file rewritten.
Returns the byte the transfer resumed at, 0 when the file came whole.
| RAISES | DESCRIPTION |
|---|---|
ChunkedEncodingError
|
fewer bytes arrived than the server announced, so a short file is retried rather than renamed into place looking finished. |
Source code in hyperproc/archive/dlr.py
download(results, out_dir: str | Path = 'data', workers: int = 4, user: str | None = None, password: str | None = None, accept_policy: bool = False, verbose: bool = True) -> list[Path]
¶
Fetch EnMAP or DESIS files into out_dir. Needs an EOC account.
Every file of a granule lands in the one directory, which is what the
readers expect: an EnMAP GeoTIFF carries no wavelengths of its own and
finds them in the METADATA.XML beside it.
A result set mixing EnMAP and DESIS is fine: each mission is fetched with
its own account if you have set one, since DLR grants access to the two
separately. See :func:credentials.
accept_policy=True agrees to DLR's Acceptable Usage Policy from here;
without it, a pending policy stops the download and says where to read it.
A file whose connection breaks is resumed from the bytes already written,
where the server allows it, for as long as each connection adds some; it
gives up after :data:RETRIES breaks in a row that add nothing. Until it
is whole it sits beside its final name as *.part, so a later call
carries on from it too, and a file shorter than the server announced is
never renamed into place.
Source code in hyperproc/archive/dlr.py
641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 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 748 749 750 | |