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 annotations (closes #164) #176

Open
wants to merge 41 commits into
base: master
Choose a base branch
from
Open

Conversation

9en9i
Copy link

@9en9i 9en9i commented Mar 14, 2024

What do these changes do?

Pull request adds annotations to methods as well as some helper classes.

Are there changes in behavior for the user?

It is expected that nothing should change for users.

Related issue number

#164

Checklist

  • I think the code is well written
  • Unit tests for the changes exist
  • Documentation reflects the changes

@9en9i
Copy link
Author

9en9i commented Mar 14, 2024

I have the first version of the pull request ready. I ran the tests on python 3.8 and python 3.12. There are some things that I think need to be clarified. Such as:

https://github.com/aio-libs/aioftp/pull/176/files#diff-0cb34502f4f31cd519adc377a42fa19735a3069781423362a8d21f71d53b3957R273

@pohmelie pohmelie changed the title Adding type annotations add type annotations (closes #164) Mar 14, 2024
pyproject.toml Outdated Show resolved Hide resolved
pyproject.toml Outdated Show resolved Hide resolved
pyproject.toml Outdated Show resolved Hide resolved
src/aioftp/__main__.py Outdated Show resolved Hide resolved
src/aioftp/client.py Outdated Show resolved Hide resolved
for fact in facts_found[:-1].split(";"):
key, _, value = fact.partition("=")
entry[key.lower()] = value
return pathlib.PurePosixPath(name), entry

return pathlib.PurePosixPath(name), cast(InfoDict, entry)
Copy link
Collaborator

Choose a reason for hiding this comment

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

If you use cast between dictionaries in one place, the you should use it in all places. If you decide to make strict typing, then don't use cast.

Copy link
Author

Choose a reason for hiding this comment

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

It is impossible to type every part of the code because python is very dynamic. It's also impossible to do without cast. The input is just bytes/strings and whether they are cast to the desired structure depends on who uses the function.

Copy link
Author

Choose a reason for hiding this comment

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

In the other case here, you have to do some validation on a few lines and throw an error.

Copy link
Collaborator

Choose a reason for hiding this comment

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

In this case you can add a MLSxDict or something like that, which will use only required for further code keys (like "type") and type checker will help here, by the way. And after all InfoDict will be a union of three (or even more) type dicts.

Copy link
Author

Choose a reason for hiding this comment

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

So, here I don't really understand what you're suggesting.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I suggest to add MLSxDict with required fields and add it to InfoDict union.

src/aioftp/client.py Outdated Show resolved Hide resolved
src/aioftp/client.py Outdated Show resolved Hide resolved
src/aioftp/client.py Show resolved Hide resolved
src/aioftp/client.py Outdated Show resolved Hide resolved
_PathType: TypeAlias = Union[str, pathlib.PurePosixPath]


_FAIL_CODE: TypeAlias = Literal["503", "425"]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why this is a Literal? It looks like the ode can be any string

message: str


_Future: TypeAlias = "asyncio.Future[Any]"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why it is a "asyncio.Future" instead of asyncio.Future?

user_required = ("user", "no user (use USER firstly)")
login_required = ("logged", "not logged in")
passive_server_started = (
user_required: ClassVar[_ConnectionCondition] = _ConnectionCondition("user", "no user (use USER firstly)")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Since named tuples used, then there is no reasons to not to use names.

timeout = getattr(cls, name)
return asyncio.wait_for(coro, timeout)
async def wrapper(*args: _PS.args, **kwargs: _PS.kwargs) -> _T:
self: "AsyncPathIO" = get_param((0, "self"), args, kwargs)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why we need this get_param method, and not use types directly on function arguments definition?

@ConnectionConditions(
ConnectionConditions.data_connection_made,
wait=True,
fail_code="425",
fail_info="Can't open data connection",
)
@worker
async def stor_worker(self, connection, rest):
async def stor_worker(self: Self, connection: ConnectionProtocol, rest: object) -> 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 self is typed with Self only here if it needed?

else:
code, info = "550", "path unreachable"
code, info = "550", ["path unreachable"]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why add squared brackets only here? Other places are ok without brackets.

@@ -1447,11 +1655,11 @@ async def _start_passive_server(self, connection, handler_callback):
ssl=self.ssl,
**self._start_server_extra_arguments,
)
return passive_server
return passive_server # type: ignore
Copy link
Collaborator

Choose a reason for hiding this comment

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

This looks like it is always Server

from typing import Any, Dict, Tuple


def get_param(where: Tuple[int, str], args: Tuple[Any, ...], kwargs: Dict[str, Any]) -> Any:
Copy link
Collaborator

Choose a reason for hiding this comment

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

This method (if it will be presented) should be in common I think

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