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

Dict construction of sequence of EntryPoint objects is unintuitive #284

Closed
jaraco opened this issue Feb 21, 2021 · 5 comments
Closed

Dict construction of sequence of EntryPoint objects is unintuitive #284

jaraco opened this issue Feb 21, 2021 · 5 comments

Comments

@jaraco
Copy link
Member

jaraco commented Feb 21, 2021

In pypa/twine#728, I learned that the dict hack for resolving a group of entry points by name is unintuitive, violates static type checks, and still doesn't provide an elegant way to resolve a set of entry points by group and name.

Therefore, I'd like to re-consider the user-interface (API) for entry_points() and its objects. In particular, I want to go back to the drawing board, revisit the requirements, and design something that's simple and elegant in the general case, but also isn't constrained just to the common cases.

This bug represents the motivation for creating #278 and to be more explicit about the motivations and factors driving the change.

@jaraco
Copy link
Member Author

jaraco commented Feb 21, 2021

In the twine issue, I attempted to implement this function:

def _registered_commands(
    group: str = "twine.registered_commands",
) -> Dict[str, importlib_metadata.EntryPoint]:
    return dict(importlib_metadata.entry_points()[group])

Unfortunately, this approach led to failures in type checks, namely:

twine/cli.py:33: error: Argument 1 to "dict" has incompatible type "Tuple[EntryPoint, ...]"; expected "Iterable[Tuple[str, EntryPoint]]"

I reported the issue in python/mypy#9938, but the initial response is that this (ab)use of Python isn't supported by mypy (mypy can't be smart enough to detect that __iter__ always returns a two-tuple, so can't detect that a sequence of EntryPoint is a suitable input for the dict constructor.

As a result, I'd like to have a better approach that still meets this very common use-case.

@jaraco
Copy link
Member Author

jaraco commented Feb 21, 2021

Some of the requirements I've inferred from the past experience:

  1. The most common use-cases are:
    • Query all entry points in a group.
      • Within that group, enumerate all names.
      • Given a name, resolve the entry point.
    • Query a specific single entry point by group and name (I wonder if this is actually an important, common case).
    • Query all entry points for a dist in a group.
  2. Most common use-cases of entry points should be met with a single import. It should not be necessary to import more than one name to query the EntryPoints for the common cases.
  3. It should be possible to return the results above on one line (~68 chars, allowing for three levels of indentation, including modest group and name values) without any branching logic (simplicity and brevity).
  4. Support less common use-cases through meaningful result objects.
  5. If no entry points for a group exists, that should be equivalent to there being no entry points (empty set).
  6. The solution should be intuitive and not violate static type checking limitations.

The current design achieves much of this through the following implementation:

  • entry_points() returns a dict of sequences of EntryPoint objects keyed by group.
  • Sequences of entry points can be iterated over or passed to a dict constructor, creating a mapping of EntryPoint objects by name.
  • A Distribution also has an entry_points property that returns a sequence of EntryPoint objects for that distribution.

The current implementation doesn't support (5) above, but instead requires the user to resolve a missed key to an empty sequence (e.g. entry_points().get(group, ())).

The current implementation doesn't satisfy (6), the primary motivation for making the change.

@jaraco
Copy link
Member Author

jaraco commented Feb 21, 2021

Exploring solutions

Sequence of EntryPoint objects as class

In #278, I've explored a couple of approaches that attempt to address the concerns above, first by elevating the concept of a sequence of entry points as its own class, EntryPoints, with some interface for easily retrieving the names of the entry points and selecting the entry point with a matching name.

Sequence of EntryPoint objects resolvable by group

Extending the idea of the EntryPoints to the broader set of entry points not yet resolved by group, I thought the same approach could be applied earlier in the call chain to also return an GroupedEntryPoints, which provides similar conveniences before the group has been resolved (and also compatibility with the dict object returned by entry_points).

This approach had the nice feature of being explicit about the state of resolution while resolving entry points, but also was fairly constraining and a lot of cognitive overhead.

Sequence of EntryPoint objects selectable by name or group

Combining the two ideas above, I found that with the introduction of a new .select() method on EntryPoints, such an object could serve both purposes (pre- and post- group resolution). This approach also had the unfortunate downside of diverging fairly starkly from the dict-like behaviors present in the current implementation.

@jaraco
Copy link
Member Author

jaraco commented Feb 24, 2021

Fixed in #278.

@jaraco jaraco closed this as completed Feb 24, 2021
This was referenced Mar 15, 2021
@vishwesh-vishwesh
Copy link

I am not sure if this is relevant here, I get this error, "TypeError: entry_points() got an unexpected keyword argument 'group'"
for twine upload dict*/ . Is it still an issue or is there a solution now?

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

No branches or pull requests

2 participants