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

Set SpecificFlowClient default scope object and adjust utils.classproperty #793

Merged

Conversation

sirosen
Copy link
Member

@sirosen sirosen commented Jul 26, 2023

There are two changes here.
Primarily, this changeset drives at improving the behavior at typing time and runtime for users of SpecificFlowClient.

  1. mypy should allow scope: str = myclient.scopes.user -- for this, we need to manipulate the scopes classvar a little.
  2. SpecificFlowClient.scopes.user should still error, but with a useful message which points at instance-only usage

In service of these changes, it turns out that our internal classproperty descriptor needs a minor adjustment. Although it is only used to implement the BaseClient.resource_server classproperty today, some pains are taken here to ensure that the behavior is kept unsurprising for future uses.


📚 Documentation preview 📚: https://globus-sdk-python--793.org.readthedocs.build/en/793/

The behavior of preferring an instance over a class, although valid
and normal for a descriptor, is unlike classmethod and therefore may
be surprising. However, it better allows us to define differentiated
behaviors between instances and their classes, especially with respect
to the `scopes` object on client classes.

Without this change, it is not possible for a classproperty, when
called, to refer back to the instance on which it was invoked.
However, it is always possible to find the class via the instance, so
expressive power is only increased by this change.

Institute a convention of the classproperty decorated method accepting
`self_or_cls` to clarify this at the usage sites.

Comments on `classproperty` are updated. In particular, make sure that
the current status of `classmethod(property(...))` stacking is
reflected in those comments.

A new test validates that the class attribute remains valid but the
instance attribute is preferred when a classproperty is used to access
an attribute.
`mypy` would flag the `scopes` object as `ScopeBuilder | None` even
though the class always defined a de-facto value for this attribute on
init.

To resolve, insert a stub object as a class variable. Not only does
this pacify mypy, but it also allows us to raise more useful errors
when the classvar is accessed.
Define the stub and several tests for it, including a typing test.
Copy link
Member

@kurtmckee kurtmckee left a comment

Choose a reason for hiding this comment

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

This is fine, but I have a feeling that the amount of code to support mypy, code to overcome mypy constraints and limitations, code to fix Sphinx documentation, comments that change linter behavior, and comments that explain why weird things exist, are -- taken together -- contorting the codebase with dubious payoff.

@sirosen
Copy link
Member Author

sirosen commented Jul 26, 2023

I tend to agree WRT classproperty. It is probably something I never would have implemented if it didn't exist in 3.9 and seem like it was going to become part of the language in all future versions.
Now that it's here, I don't really want to drop it or introduce a metaclass just to replace it. Maybe in SDK v4 we quietly change this, but I'm not exactly sure how yet.

Sans classproperty, I think inserting the stub object is useful, in that SpecificFlowClient is unlike most other clients, having no scopes at the class level but having them at the instance level, and this is a way of making that clearer both to mypy and to users.

Maybe there's a more refined approach to doing this, but again, I think v4.0 would be a good time to think about it. Perhaps we can reshape the BaseClient behavior and expectations to better match SpecificFlowClient (and GCSClient, which is similar).

pylint complains about an `__init__` not invoking `super().__init__`.
If we do the "obvious" thing and call the superclass init with a
placeholder value, attribute access patterns change.

In particular, `self.resource_server` is assigned as an attribute,
meaning that the concrete value there is discovered by the default
`__getattribute__` before `__getattr__` can be invoked. To solve this,
intercept that access with a custom `__getattribute__` to raise an
error. The error will implicitly be handled and trigger a call of
`__getattr__`, resulting in the desired `AttributeError` for
`resource_server` access.
@sirosen
Copy link
Member Author

sirosen commented Jul 26, 2023

I had to push another change to pacify pylint, but I'm going to move forward with the merge based on the prior review.

@sirosen sirosen merged commit 76e3970 into globus:main Jul 26, 2023
13 checks passed
@sirosen sirosen deleted the specific-flow-client-default-scope-object branch July 26, 2023 19:54
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.

2 participants