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

add type hints to base.py #1302

Open
wants to merge 14 commits into
base: typehints
Choose a base branch
from
Open

Conversation

Dr-Blank
Copy link
Contributor

@Dr-Blank Dr-Blank commented Nov 16, 2023

Description

Support mypy and/or pyright for the base.py

related #1296

Type of change

Please delete options that are not relevant.

  • New feature (non-breaking change which adds functionality)

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas

@Dr-Blank Dr-Blank marked this pull request as ready for review November 17, 2023 03:27
plexapi/audio.py Outdated Show resolved Hide resolved
plexapi/base.py Show resolved Hide resolved
plexapi/base.py Outdated Show resolved Hide resolved
plexapi/base.py Outdated Show resolved Hide resolved
plexapi/base.py Show resolved Hide resolved
@Dr-Blank
Copy link
Contributor Author

Is there any task remaining for this PR?

Copy link
Contributor

@ReenigneArcher ReenigneArcher left a comment

Choose a reason for hiding this comment

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

I have some comments/suggestions.

Some of my comments apply in multiple places, but I only made the comment once. For example the kwargs type hint comment.

Note, I am not a maintainer of this project, but a contributor and user of it.

plexapi/client.py Outdated Show resolved Hide resolved
plexapi/playlist.py Show resolved Hide resolved
plexapi/server.py Outdated Show resolved Hide resolved
plexapi/utils.py Outdated Show resolved Hide resolved
plexapi/utils.py Outdated Show resolved Hide resolved
plexapi/audio.py Show resolved Hide resolved
plexapi/base.py Outdated Show resolved Hide resolved
plexapi/base.py Outdated Show resolved Hide resolved
plexapi/base.py Outdated Show resolved Hide resolved
plexapi/base.py Outdated Show resolved Hide resolved
- annotations import in all files using typehints
- typehint specific imports under type_checking condition
- native library imports > installed libs > module import
Copy link
Collaborator

@JonnyWong16 JonnyWong16 left a comment

Choose a reason for hiding this comment

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

I have only done a quick review on GitHub. I haven't actually checked out this PR in my code editor to check.

plexapi/base.py Outdated Show resolved Hide resolved
plexapi/base.py Outdated
Comment on lines 7 to 8
from typing import (TYPE_CHECKING, Any, Callable, Dict, List, Optional, Set,
Type, TypeVar, Union, cast, overload)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Use individual lines (I want to eventually use Black formatting). Similar other files.

Suggested change
from typing import (TYPE_CHECKING, Any, Callable, Dict, List, Optional, Set,
Type, TypeVar, Union, cast, overload)
from typing import (
TYPE_CHECKING,
Any,
Callable,
Dict,
List,
Optional,
Set,
Type,
TypeVar,
Union,
cast,
overload,
)

Copy link
Contributor Author

@Dr-Blank Dr-Blank Jan 16, 2024

Choose a reason for hiding this comment

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

default config or have something in mind for this project?

  • all single quotes will be converted to double unless "--skip-string-normalization" is passed
  • all docstrings whitespaces will be stripped
  • should a pyproject.toml be created for black config?

plexapi/base.py Outdated Show resolved Hide resolved
plexapi/base.py Outdated
""" Builds the details key with the XML include parameters.
All parameters are included by default with the option to override each parameter
or disable each parameter individually by setting it to False or 0.
"""
details_key = self.key
details_key = str(self.key)
Copy link
Collaborator

Choose a reason for hiding this comment

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

If self.key is None then str(None) turns into 'None' so you end up return 'None'.

Copy link
Contributor Author

@Dr-Blank Dr-Blank Jan 16, 2024

Choose a reason for hiding this comment

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

good catch, removed casting

side effect: d4fc723#r1452965661

""" Returns True if this object is a child of the given attributes.
This will search the parent objects all the way to the top.

Parameters:
**kwargs (dict): The attributes and values to search for in the parent objects.
See all possible `**kwargs*` in :func:`~plexapi.base.PlexObject.fetchItem`.
"""
obj = self
obj = self # ? Why is this needed?
Copy link
Collaborator

Choose a reason for hiding this comment

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

So you don't mutate self in the while loop.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

isn't object address copied here? or am i missing something?

class Car:
    def __init__(self, name: str, color: str) -> None:
        self.name = name
        self.color = color

    def _dont_mutate_me(self) -> None:
        obj = self  # only memory address of self is copied not the object
        obj.name = "foo"  # will mutate self.name
        assert id(obj) == id(self)  # True

    def __str__(self) -> str:
        return f"I am a {self.color} {self.name}."


def main() -> None:
    car = Car(name="Tesla", color="red")
    print(car)  # I am a red Tesla.
    car._dont_mutate_me()
    print(car)  # I am a red foo.


if __name__ == "__main__":
    main()

plexapi/base.py Outdated Show resolved Hide resolved
plexapi/base.py Outdated Show resolved Hide resolved
plexapi/base.py Outdated Show resolved Hide resolved
plexapi/base.py Outdated Show resolved Hide resolved
plexapi/media.py Show resolved Hide resolved
@JonnyWong16 JonnyWong16 changed the base branch from master to typehints January 16, 2024 00:14
@JonnyWong16
Copy link
Collaborator

JonnyWong16 commented Jan 16, 2024

I also created a new branch for typehints. I think it would be better to finish type hinting the entire library before merging into master.

- fix _INCLUDES attr by including an empty dict
- add explicit return types for methods missing them
Copy link
Contributor Author

@Dr-Blank Dr-Blank left a comment

Choose a reason for hiding this comment

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

marked side effect of suggestion

Comment on lines -234 to -235
if ekey is None:
raise BadRequest('ekey was not provided')
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is guard clause removed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

def fetchItems(
       self,
       ekey: Union[str, List[int]],
       cls: Optional[Type[PlexObjectT]] = None,
       container_start: Optional[int] = None,
       container_size: Optional[int] = None,
       maxresults: Optional[int] = None,
       **kwargs: Any,
   ):

ekey is never None as defined by the method, hence unnecessary check. could typehint to accept None but then calling fetchItems without passing ekey does not make sense for the method, None should be handled by the caller imo

Comment on lines +360 to +361
if not data:
return []
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

.query can return None and .findItems does not accept None. so it should be handled in some way. returned empty list as .fetchItems returns a list

Copy link
Contributor Author

Choose a reason for hiding this comment

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

on a second thought maybe returning result is a better choice instead of empty list? or should raise error? what is the preferred way to handle no date? other choice is to make query raise an error instead of returning None

Comment on lines +546 to +547
if data is None:
raise NotFound(f'Unable to find elem: {key=}')
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this necessary?

Copy link
Contributor Author

@Dr-Blank Dr-Blank Jan 16, 2024

Choose a reason for hiding this comment

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

as long as query could return None, handling of None should be done, it is done here as next step requires subscripting data

self._loadData(data[0])  # None is not subscriptable

self._server = server
self._data = data
self._initpath = initpath or self.key
self._parent = weakref.ref(parent) if parent is not None else None
self._details_key = None
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is this removed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

being assigned after few lines as below and not used before that assignment and this removal, hence the statement has no effect

self._details_key = self._buildDetailsKey()

Comment on lines +1092 to +1093
if not data:
raise NotFound(f'Unable to find elem: {key=}')
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

   for elem in data:  # None not iterable

plexapi/collection.py Outdated Show resolved Hide resolved
plexapi/library.py Outdated Show resolved Hide resolved
plexapi/photo.py Outdated Show resolved Hide resolved
plexapi/server.py Outdated Show resolved Hide resolved
plexapi/video.py Outdated Show resolved Hide resolved
@Dr-Blank
Copy link
Contributor Author

Dr-Blank commented Mar 3, 2024

should I break this PR further down? Maybe on a per function basis? That should make it easier for review as currently this change is maybe so huge that it is daunting to start reviewing, and multiple threads going on for each change.

@Dr-Blank
Copy link
Contributor Author

I also created a new branch for typehints. I think it would be better to finish type hinting the entire library before merging into master.

I'm considering whether it would be more beneficial to merge it into master directly. Here are my thoughts:

  1. Even adding type hints to only base.py could greatly enhance readability, providing immediate benefits to users and contributors with making new PRs.
  2. By the time the entire project is type hinted, both branches may have diverged significantly, potentially leading to merge conflicts. Merging directly could mitigate this issue.
  3. I'm planning to break down PRs on a per-function basis. Since type hints are primarily for developers and don't affect code execution, they're unlikely to introduce breaking changes.

@JonnyWong16, I'd appreciate your thoughts on this. Do you think the benefits of direct merging outweigh any potential risks?

either way I will close this PR and start anew since this is too huge an undertaking for review.

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

Successfully merging this pull request may close these issues.

5 participants