Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simpler extension interface #1243

Merged
merged 13 commits into from
Oct 12, 2023
Merged

Simpler extension interface #1243

merged 13 commits into from
Oct 12, 2023

Conversation

jsignell
Copy link
Member

Related Issue(s):

Description:

When the extension is implemented:

import pystac

PROJ_EXT_EXAMPLE_URL = "https://raw.githubusercontent.com/stac-extensions/projection/v1.1.0/examples/item.json"

item = pystac.Item.from_file(PROJ_EXT_EXAMPLE_URL)
item.ext.proj.epsg
# 32659

When the extension isn't implemented:

item.ext.eo
---------------------------------------------------------------------------
ExtensionNotImplemented                   Traceback (most recent call last)
Cell In[5], line 1
----> 1 item.ext.eo

File ~/pystac/pystac/extensions/ext.py:47, in ItemExt.eo(self)
     45 @property
     46 def eo(self) -> EOExtension[pystac.Item]:
---> 47     return EOExtension.ext(self.stac_object)

File ~/pystac/pystac/extensions/eo.py:408, in EOExtension.ext(cls, obj, add_if_missing)
    397 """Extends the given STAC Object with properties from the
    398 :stac-ext:`Electro-Optical Extension <eo>`.
    399 
   (...)
    405     pystac.ExtensionTypeError : If an invalid object type is passed.
    406 """
    407 if isinstance(obj, pystac.Item):
--> 408     cls.validate_has_extension(obj, add_if_missing)
    409     return cast(EOExtension[T], ItemEOExtension(obj))
    410 elif isinstance(obj, pystac.Asset):

File ~/pystac/pystac/extensions/base.py:201, in ExtensionManagementMixin.validate_has_extension(cls, obj, add_if_missing)
    198     cls.add_to(obj)
    200 if not cls.has_extension(obj):
--> 201     raise pystac.ExtensionNotImplemented(
    202         f"Could not find extension schema URI {cls.get_schema_uri()} in object."
    203     )

ExtensionNotImplemented: Could not find extension schema URI https://stac-extensions.github.io/eo/v1.1.0/schema.json in object.

Add an extension

item.ext.add("eo")

PR Checklist:

  • pre-commit hooks pass locally
  • Tests pass (run scripts/test)
  • Documentation has been updated to reflect changes, if applicable
  • This PR maintains or improves overall codebase code coverage.
  • Changes are added to the CHANGELOG. See the docs for information about adding to the changelog.

@codecov
Copy link

codecov bot commented Sep 27, 2023

Codecov Report

Attention: 57 lines in your changes are missing coverage. Please review.

Files Coverage Δ
pystac/asset.py 97.14% <100.00%> (+0.11%) ⬆️
pystac/collection.py 93.05% <100.00%> (+0.09%) ⬆️
pystac/extensions/classification.py 95.03% <100.00%> (+0.01%) ⬆️
pystac/extensions/file.py 91.85% <100.00%> (+0.06%) ⬆️
pystac/extensions/grid.py 96.29% <100.00%> (+0.06%) ⬆️
pystac/extensions/item_assets.py 95.57% <100.00%> (+0.20%) ⬆️
pystac/extensions/label.py 93.33% <100.00%> (+0.01%) ⬆️
pystac/extensions/mgrs.py 98.71% <100.00%> (+0.01%) ⬆️
pystac/extensions/raster.py 90.25% <100.00%> (+0.03%) ⬆️
pystac/extensions/scientific.py 95.03% <100.00%> (+0.03%) ⬆️
... and 16 more

📢 Thoughts on this report? Let us know!.

Copy link
Member

@gadomski gadomski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concept seems good to me! Hopefully this will be a pretty low maintenance burden once it gets set up, since most of the work is still in the extensions themselves.

@jsignell jsignell force-pushed the ext branch 3 times, most recently from 91e76c1 to 762fe7b Compare September 29, 2023 16:04
@jsignell
Copy link
Member Author

okie dokie! It's greeeeennnn over here

@jsignell jsignell marked this pull request as ready for review September 29, 2023 16:05
@jsignell jsignell requested a review from gadomski September 29, 2023 16:06
@jsignell
Copy link
Member Author

I guess I can do the docs updates on this branch as well.

Copy link
Member

@gadomski gadomski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No notes on the impl, I'm 👍🏼 ^ 2. Looks great. I think we just need docstrings on the functions, and maybe a "Making a new extension class" tutorial? The tutorial could be its own ticket too if you want to push this in first.

pystac/asset.py Show resolved Hide resolved
pystac/extensions/ext.py Show resolved Hide resolved
@philvarner
Copy link
Collaborator

  1. I'd prefer if item.ext.eo returned None rather than throwing an Exception. I guess it's more Pythonic to throw an exception for everything, but dislike that convention, since it's not an error condition if the Item doesn't implement the extension.

With that, I could write code as:

if eo := item.ext.eo:
  print(eo.cloud_cover)

instead of:

try:
  print(eo.cloud_cover)
except ExtensionNotImplemented:
  pass
  1. I'd prefer if item.ext.add, instead of accepting a raw string parameter (presumably the Field Name Prefix?) took a parameter of a custom type (e.g., Extension) that all extensions had to implement, as a form of dynamic enum. That would give better typechecking than just a string, which in practice is pretty error-prone.

@gadomski
Copy link
Member

gadomski commented Oct 2, 2023

That would give better typechecking than just a string, which in practice is pretty error-prone.

I think the use of Literal here makes the type checking better: https://github.com/stac-utils/pystac/pull/1243/files#diff-3a39d1c18e4343de9e2ad0a08ee71c3e616589cbc3677eeac625e679f195ea16R27-R47.

took a parameter of a custom type (e.g., Extension)

I believe @jsignell started with an Enum implementation, but it felt clunky. I think a soft design goal here might be "access and set extension attributes without additional imports"?

I'd prefer if item.ext.eo returned None rather than throwing an Exception.

The only downside here is that for type checking, item.ext.eo then becomes Optional[Extension], so you have to do assert item.ext.eo before accessing the attributes to make mypy happy. The "throw if absent" pattern makes the "I know it's there" use case slightly less verbose. 🤷🏼

@jsignell
Copy link
Member Author

jsignell commented Oct 2, 2023

I'd prefer if item.ext.eo returned None rather than throwing an Exception.

The only downside here is that for type checking, item.ext.eo then becomes Optional[Extension], so you have to do assert item.ext.eo before accessing the attributes to make mypy happy. The "throw if absent" pattern makes the "I know it's there" use case slightly less verbose. 🤷🏼

I didn't call it out in my example, but there is also a has method so the no-error incantation would be:

if item.ext.has("eo"):
    print(item.ext.eo.cloud_cover)

Does that feel better?

@philvarner
Copy link
Collaborator

I'm fine with whatever y'all decide on.

@jsignell jsignell requested a review from gadomski October 12, 2023 15:51
@jsignell
Copy link
Member Author

Finally circled back. I took a pass at the docs and tutorials as well.

@gadomski
Copy link
Member

🎉 thanks for this @jsignell really stoked about it.

@gadomski gadomski enabled auto-merge October 12, 2023 17:58
@gadomski gadomski added this pull request to the merge queue Oct 12, 2023
Merged via the queue into stac-utils:main with commit bf2a29b Oct 12, 2023
18 checks passed
@jsignell jsignell deleted the ext branch October 12, 2023 20:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Simpler extension interface
4 participants