Skip to content

Worse developer experience since introducing async support - IntelliSense doesn't work for endpoint classes #483

Closed

Description

@michaeloliverx

Some background first:
I used this SDK in a previous company with a very positive experience, fast forward a few years I find myself working with auth0 in python again only to find that the DX is much worse :(

Describe the problem

It looks like since the introduction of asyncio support that the attributes are dynamically added:

def __init__(self, domain, token, rest_options=None):
for name, cls in modules.items():
setattr(
self,
name,
cls(domain=domain, token=token, rest_options=rest_options),
)

This doesn't play well with static analysis thus there is no autocomplete / intellisense for any of the endpoint classes.

image

image

This also means that type checking via mypy / pyright etc doesn't work.

What was the expected behavior?

I expect all the endpoint classes to be listed as attributes.

This was the behaviour of v3.22.0:
image

Reproduction

from auth0.management import Auth0

auth0_client = Auth0("", "")

auth0_client.users

Environment

  • Version of this library used: 4.1.0
  • Which framework are you using, if applicable:
  • Other modules/plugins/libraries that might be involved:
  • Any other relevant information you think would be useful:

Activity

  1. marcusmotill commented on Mar 20, 2023

    @marcusmotill

    Agreed, also making pylint go wild:
    image

  2. mardav10 commented on Mar 20, 2023

    @mardav10

    +1

  3. Viicos commented on Mar 23, 2023

    @Viicos
    Contributor

    Hi,

    I'm currently working on adding types to this sdk (on my free time). Once #472 gets merged, I'll continue working on the management module, and I'll be able to fix this.

  4. michaeloliverx commented on Mar 23, 2023

    @michaeloliverx
    Author

    Hi,

    I'm currently working on adding types to this sdk (on my free time). Once #472 gets merged, I'll continue working on the management module, and I'll be able to fix this.

    Nice work on getting that started, but I also think its important for the core team to embrace type hints for it to be successful. If there is no type checking in the CI then the type hints could quickly become out of date, wrong type hints are worse than none.

    For the core team it might be worth investigating some sort of codegen solutions if the maintenance of the types is too much e.g. https://github.com/Azure/autorest / https://github.com/Azure/autorest.python

  5. Viicos commented on Mar 23, 2023

    @Viicos
    Contributor

    I've added a mypy configuration file in the linked PR, taking into account the fact that maintainers can't fully keep return types (e.g. with TypedDict) up to date. CI will probably be updated once I'll finish working on typing

  6. adamjmcgrath commented on Mar 27, 2023

    @adamjmcgrath
    Contributor

    Hi @michaeloliverx - thanks for raising this

    The _async methods are added at runtime so not available for static analysis, other than that we have not made any other changes to the SDK that should make it worse in terms of DX.

    For the core team it might be worth investigating some sort of codegen solutions if the maintenance of the types is too much e.g. https://github.com/Azure/autorest / https://github.com/Azure/autorest.python

    We are investigating codegen solutions for a next major, but for the time being the async methods will remain as they are.

  7. michaeloliverx commented on Mar 27, 2023

    @michaeloliverx
    Author

    Hi @adamjmcgrath thanks for taking the time to reply.

    The _async methods are added at runtime so not available for static analysis, other than that we have not made any other changes to the SDK that should make it worse in terms of DX.

    This is actually not true, if we ignore the async methods for now and concentrate solely on the sync client then you will see that the DX has definitely decreased. From v3.22.0 and below the endpoint classes were set statically within the constructor of the Auth0 class:

    def __init__(self, domain, token, rest_options=None):
    self.actions = Actions(domain=domain, token=token, rest_options=rest_options)
    self.attack_protection = AttackProtection(domain=domain, token=token, rest_options=rest_options)
    self.blacklists = Blacklists(domain=domain, token=token, rest_options=rest_options)
    self.client_grants = ClientGrants(domain=domain, token=token, rest_options=rest_options)
    self.clients = Clients(domain=domain, token=token, rest_options=rest_options)
    self.connections = Connections(domain=domain, token=token, rest_options=rest_options)
    self.custom_domains = CustomDomains(domain=domain, token=token, rest_options=rest_options)
    self.device_credentials = DeviceCredentials(domain=domain, token=token, rest_options=rest_options)
    self.email_templates = EmailTemplates(domain=domain, token=token, rest_options=rest_options)
    self.emails = Emails(domain=domain, token=token, rest_options=rest_options)
    self.grants = Grants(domain=domain, token=token, rest_options=rest_options)
    self.guardian = Guardian(domain=domain, token=token, rest_options=rest_options)
    self.hooks = Hooks(domain=domain, token=token, rest_options=rest_options)
    self.jobs = Jobs(domain=domain, token=token, rest_options=rest_options)
    self.log_streams = LogStreams(domain=domain, token=token, rest_options=rest_options)
    self.logs = Logs(domain=domain, token=token, rest_options=rest_options)
    self.organizations = Organizations(domain=domain, token=token, rest_options=rest_options)
    self.prompts = Prompts(domain=domain, token=token, rest_options=rest_options)
    self.resource_servers = ResourceServers(domain=domain, token=token, rest_options=rest_options)
    self.roles = Roles(domain=domain, token=token, rest_options=rest_options)
    self.rules_configs = RulesConfigs(domain=domain, token=token, rest_options=rest_options)
    self.rules = Rules(domain=domain, token=token, rest_options=rest_options)
    self.stats = Stats(domain=domain, token=token, rest_options=rest_options)
    self.tenants = Tenants(domain=domain, token=token, rest_options=rest_options)
    self.tickets = Tickets(domain=domain, token=token, rest_options=rest_options)
    self.user_blocks = UserBlocks(domain=domain, token=token, rest_options=rest_options)
    self.users_by_email = UsersByEmail(domain=domain, token=token, rest_options=rest_options)
    self.users = Users(domain=domain, token=token, rest_options=rest_options)

    From any version afterwards they are set dynamically:

    def __init__(self, domain, token, rest_options=None):
    for name, cls in modules.items():
    setattr(
    self,
    name,
    cls(domain=domain, token=token, rest_options=rest_options),
    )

    I think it would be a definite win for users if these could be set statically again.

  8. Viicos commented on Mar 27, 2023

    @Viicos
    Contributor

    @michaeloliverx I'm currently working on a second PR to add types, and I'm taking care of the issue you're mentioning 😊

    Edit: This is how I've done it. Regarding the async client, it's a bit more difficult, as classes are created dynamically.

  9. adamjmcgrath commented on Mar 27, 2023

    @adamjmcgrath
    Contributor

    Ah, ok - thanks for clarifying @michaeloliverx

    And thanks for providing a fix @Viicos - I was away last week and am catching up with a few things, but will take a look at your PR as soon as I can

  10. marcusmotill commented on Mar 29, 2023

    @marcusmotill

    can we reopen this? the issue is not resolved.

  11. adamjmcgrath commented on Apr 3, 2023

    @adamjmcgrath
    Contributor

    Sure - have raised #486 to fix

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions