- Notifications
You must be signed in to change notification settings - Fork 435
Add typings#577
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
base:master
Are you sure you want to change the base?
Add typings #577
Uh oh!
There was an error while loading. Please reload this page.
Conversation
bryanforbes commented May 18, 2020 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
bryanforbes commented May 19, 2020 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
One thing to note is that the handling of classMyRecord(asyncpg.Record): id: intname: strcreated_at: datetime.datetimer: MyRecord= ... reveal_type(r.id) # intThe user will only use this class for type checking and can use attribute access with type checking, but not lookup notation. A mypy plugin can probably be written to handle the lookup notation ( records: typing.List[MyRecord] =awaitconn.fetch('SELECT ... from ...')It's an error because
record: MyRecord=awaitconn.fetchrow('SELECT ... FROM ...') records: typing.List[MyRecord] =awaitconn.fetch('SELECT ... FROM ...') cursor: asyncpg.cursor.CursorFactory[MyRecord] =awaitconn.cursor('SELECT ... FROM ...') record2: asyncpg.Record=awaitconn.fetchrow('SELECT ... FROM ...') records2: typing.List[asyncpg.Record] =awaitconn.fetch('SELECT ... FROM ...') cursor2: asyncpg.cursor.CursorFactory[asyncpg.Record] =awaitconn.cursor('SELECT ... FROM ...')
# the variables below have the same types as the last code block,# but the types are inferred insteadrecord=awaitconn.fetch('SELECT ... FROM ...', record_class=MyRecord) records=awaitconn.fetch('SELECT ... FROM ...', record_class=MyRecord) cursor=awaitconn.cursor('SELECT ... FROM ...', record_class=MyRecord) record2=awaitconn.fetchrow('SELECT ... FROM ...') records2=awaitconn.fetch('SELECT ... FROM ...') cursor2=awaitconn.cursor('SELECT ... FROM ...')Note that while I have the second option coded locally, but I wanted to check if adding that extra |
elprans commented May 19, 2020
There's a third option: lie about the return type and say it's a |
bryanforbes commented May 19, 2020
Typing it as |
elprans commented May 19, 2020
Right. Well, my concern with the You'd mentioned that a plugin is necessary to handle subscript access, so perhaps said plugin can also deal with the |
bryanforbes commented May 19, 2020
I understand the concern and share your concern. Would
I'll have to do some digging to see if this is possible. If it is, I agree that it would probably be the best solution. |
bryanforbes commented Jun 4, 2020 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
I've done quite a bit more work on the mypy plugin and have the following working: importasyncpgimportdatetimeimporttypingimporttyping_extensionsclassMyRecord(asyncpg.Record): foo: intbar: typing.Optional[str] classMyOtherRecord(MyRecord): baz: datetime.datetimeasyncdefmain() ->None: conn=awaitasyncpg.connect(...) m=typing.cast(MyRecord, awaitconn.fetchrow('SELECT foo, bar FROM records')) o=typing.cast(MyOtherRecord, awaitconn.fetchrow('SELECT foo, bar, baz FROM other_records')) key='baz'fkey: typing_extensions.Final='baz'reveal_type(m['foo']) # intreveal_type(m['bar']) # Optional[str]reveal_type(m['baz']) # error: "MyRecord" has no key 'baz'reveal_type(m[0]) # intreveal_type(m[1]) # Optional[str]reveal_type(m[2]) # error: "MyRecord" has no index 2reveal_type(m.get('foo')) # intreveal_type(m.get('bar')) # Optional[str]reveal_type(m.get('baz')) # error: "MyRecord" has no key 'baz'reveal_type(m.get('baz', 1)) # Literal[1]reveal_type(m.get(key, 1)) # Union[Any, int]reveal_type(m.get(fkey, 1)) # Literal[1]reveal_type(o['foo']) # intreveal_type(o['bar']) # Optional[str]reveal_type(o['baz']) # datetimereveal_type(o[0]) # intreveal_type(o[1]) # Optional[str]reveal_type(o[2]) # datetimereveal_type(o.get('foo')) # intreveal_type(o.get('bar')) # Optional[str]reveal_type(o.get('baz')) # datetimereveal_type(o.get('baz', 1)) # datetimereveal_type(o.get(key, 1)) # Union[Any, int]reveal_type(o.get(fkey, 1)) # datetimeBased on the implementation of I also did a lot of poking around to try and get the plugin to set the return type based on the variable it is being assigned to, but I don't see a way that it's possible. This means we're left with two options:
stmt=typing.cast(asyncpg.prepared_stmt.PreparedStatement[MyRecord], awaitconn.prepare('SELECT ... FROM ...')) reveal_type(stmt) # asyncpg.prepared_stmt.PreparedStatement[MyRecord]
stmt=awaitconn.prepare('SELECT ... FROM ...', return_type=MyRecord) reveal_type(stmt) # asyncpg.prepared_stmt.PreparedStatement[MyRecord]There's also a possible third option if the stmt=awaitconn.prepare('SELECT ... FROM ...') reveal_type(stmt) # asyncpg.prepared_stmt.PreparedStatement[asyncpg.protocol.protocol.Record]record=awaitstmt.fetchrow(...) reveal_type(record) # Optional[asyncpg.protocol.protocol.Record]assertisinstance(record, MyRecord) reveal_type(record) # MyRecordThe third option completely depends on whether the |
elprans commented Jun 5, 2020
Awesome work on the plugin, @bryanforbes! Thanks!
I'm still a bit uneasy with adding unused parameters just for the typing purpose. That said, if we had |
bryanforbes commented Jun 8, 2020
@elprans Thanks! There are still some places in the code where
With regards to the unused parameter, I completely understand your concern. I also think that making |
Add the new `record_class` parameter to the `create_pool()` and `connect()` functions, as well as to the `cursor()`, `prepare()`, `fetch()` and `fetchrow()` connection methods. This not only allows adding custom functionality to the returned objects, but also assists with typing (see #577 for discussion). Fixes: #40.
Add the new `record_class` parameter to the `create_pool()` and `connect()` functions, as well as to the `cursor()`, `prepare()`, `fetch()` and `fetchrow()` connection methods. This not only allows adding custom functionality to the returned objects, but also assists with typing (see #577 for discussion). Fixes: #40.
Add the new `record_class` parameter to the `create_pool()` and `connect()` functions, as well as to the `cursor()`, `prepare()`, `fetch()` and `fetchrow()` connection methods. This not only allows adding custom functionality to the returned objects, but also assists with typing (see #577 for discussion). Fixes: #40.
elprans left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With regards to the unused parameter, I completely understand your concern. I also think that making
fetch()and friends return the instances of that class would be a good solution.
I took this upon myself to implement in #559.
I did a cursory review of the PR and left a few comments. Most importantly, I think we should bite the bullet and use the PEP 526 syntax for type annotations instead of comments. Python 3.5 is rapidly going out of fashion, and I'd love to drop a bunch of hacks we already have to support it.
Thanks again for working on this!
asyncpg/cluster.py Outdated
| low, high=port_range | ||
| port=low | ||
| port=low# type: typing.Optional[int] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Python 3.5 would be EOL'd in September and I think we should just drop support for it and use proper annotation syntax everywhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've updated the PR to switch to 3.6 type annotations and removed 3.5 from CI
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
asyncpg/connection.py Outdated
| self._aborted=True | ||
| self._protocol.abort() | ||
| self._protocol=None | ||
| self._protocol=None# type: ignore[assignment] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps a cleaner solution would be to assign a sentinel instance of a Protocol-like object, e.g. DeadProtocol that raises an error on any attribute access.
d449a6f to da85005CompareAdd the new `record_class` parameter to the `create_pool()` and `connect()` functions, as well as to the `cursor()`, `prepare()`, `fetch()` and `fetchrow()` connection methods. This not only allows adding custom functionality to the returned objects, but also assists with typing (see #577 for discussion). Fixes: #40.
Add the new `record_class` parameter to the `create_pool()` and `connect()` functions, as well as to the `cursor()`, `prepare()`, `fetch()` and `fetchrow()` connection methods. This not only allows adding custom functionality to the returned objects, but also assists with typing (see #577 for discussion). Fixes: #40.
Add the new `record_class` parameter to the `create_pool()` and `connect()` functions, as well as to the `cursor()`, `prepare()`, `fetch()` and `fetchrow()` connection methods. This not only allows adding custom functionality to the returned objects, but also assists with typing (see #577 for discussion). Fixes: #40.
Add the new `record_class` parameter to the `create_pool()` and `connect()` functions, as well as to the `cursor()`, `prepare()`, `fetch()` and `fetchrow()` connection methods. This not only allows adding custom functionality to the returned objects, but also assists with typing (see #577 for discussion). Fixes: #40.
victoraugustolls commented Aug 15, 2020
@bryanforbes PR #599 was merged! I don't know if it was a blocker for this PR or not (by reading the discussion here I think it was). Thanks for this PR, really, looking forward to it! 😄 |
bryanforbes commented Aug 20, 2020
@victoraugustolls I'll rebase and update this PR today |
4fef52f to 8332c03Comparebryanforbes commented Aug 21, 2020
@victoraugustolls I finished the rebase, but there's an issue with Python 3.6 and |
victoraugustolls commented Aug 22, 2020
Thanks for the update @bryanforbes ! I will try and search for something that can help |
bryanforbes commented Aug 24, 2020
@victoraugustolls I was able to come up with a solution for the metaclass issue in Python 3.6 (that won't affect performance on 3.7+), but I'd like to work on some tests before taking this PR out of draft. Feel free to review the code, though. |
18f3e1b to 8ac22c1Comparebryanforbes commented Oct 16, 2020
@victoraugustolls I started writing unit tests last night, however they use |
elprans commented Oct 16, 2020 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
@bryanforbes I don't think we need more than running |
victoraugustolls commented Oct 16, 2020
Hi @bryanforbes ! I'm not a maintainer, but thanks for asking! I agree with @elprans and running mypy should be enough. If mypy is happy it should be fine! |
bryanforbes commented Dec 2, 2022
I need to rebase on master. I have some free time coming up in the next few weeks, so I should be able to take care of it. I'll let you know if I need some help, @Andarius. |
ethanleifer commented May 22, 2023
Is there anything blocking this pr? I would be willing to help out if needed |
takeda commented May 23, 2023
Yeah I think it should be merged. Maybe it is still not perfect, but it likely will work for majority of people and any bugs will come out when people start using. It's been 3 years this PR has been open and rebasing it constantly just adds unnecessary overhead. |
ethanleifer commented May 23, 2023
Is there someone we have to ping to get this merged? I would be willing to help out wherever is needed |
takeda commented May 23, 2023
Voldemat commented Sep 14, 2023
Hi! When you plan to merge this proposal to main? |
r614 commented Feb 6, 2024
eirnym commented Feb 12, 2024
Hi @bryanforbes, could you please resolve conflicts. I have no permissions to do that @elprans I see that this work could be continued even after this PR has been merged. |
bryanforbes commented Feb 12, 2024
I'll try to get some time to resolve conflicts this week |
fb6d8ef to 348acbbCompareAdd typings to the project and check the project using mypy. `PostgresMessage` and `PoolConnectionProxy` were broken out into their own files to make it easier to add typing via stub (pyi) files. Since they are metaclasses which generate dynamic objects, we can't type them directly in their python module.
348acbb to 276bb46Comparebryanforbes commented Feb 15, 2024
@elprans I've updated the typings to match what I have in |
bryanforbes commented Mar 4, 2024
@elprans would it help you review this to break this into individual PRs? There's a lot here. |
elprans commented Mar 4, 2024
Yes, absolutely. |
bryanforbes commented Mar 4, 2024
Ok. I'll take care of that this week. |
edgarrmondragon commented Jul 19, 2024
Been there: https://meta.stackexchange.com/questions/19478/the-many-memes-of-meta/19514#19514 😅 Are there any updates to this? |
takeda commented Oct 17, 2024
@bryanforbes is there still work on this? I found I really wish asyncpg would natively support types so we wouldn't have this type of inconsistencies. |
bitterteriyaki commented Oct 20, 2024 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
You can workaround this with something like: fromtypingimportTYPE_CHECKINGfromasyncpgimportPool, RecordasBaseRecordifTYPE_CHECKING: Record=BaseRecord[Record] # you can also specify a custom `Record` classelse: Record=BaseRecord# etc.This is a great workaround when some stub library changes some interface which is incompatible with runtime environment. |
takeda commented Oct 20, 2024
oh wow, this is a great trick. I'll install it back and try it, though I would still love if the types could be added to the base package, as this would eliminate need for things like that. |
takeda commented Oct 22, 2024
sigh I just noticed that were some initial typing added to 0.30.0 which made me excited, but that turned out to be only adding types to some less popular calls (I'm mostly interested in Connect and Pool), doesn't have Is there a way to use 0.30.0 with @elprans@bryanforbes If there are types in asyncpg-stubs already that seem to work, why we are breaking changes into parts and have several months for release instead just putting them all there. The types should not affect how the code executes. |
peter-facko commented Jan 17, 2025
You can also fix errors with generics only in stubs but not in runtime with from __future__ importannotations |
This is a work in progress and still produces errors when type checking the codebase. I added type hints to the Python files where it was feasible and updated the code of the methods to be type safe. In two places (
pool.pyandexceptions/_base.py), adding type hints to the code itself would not work because of the dynamic nature of the code in those modules. There may also be places where I'm making wrong assumptions about the code (especially in the cython code), so a thorough review would be very welcome. Lastly, I did not add typings forasyncpg._testbasesince that seems to be an internal testing module.Some of the remaining problems that mypy finds may be bugs in the code or they may be that mypy is being overly strict:
cursor.py: InBaseCursor.__repr__,self._statecould technically beNone, and cause an exception whenself._state.queryis usedconnection.py:asyncio.current_task()can returnNone, socompat.current_task()has to be typed as returningOptional[Task[Any]]. This meansConnection._cancel()may throw an exception whenself._cancellations.discard(compat.current_task(self._loop))is called_extract_stack()has a couple of issues:StackSummary.extract()but it expects a generator__path__does not exist on theasyncpgmoduleconnect_utils.pyhas several errors that relate to the code in_parse_connect_dsn_and_args()assigning new types to variables originally declared as another type. I can try to clean this up to make it more type-safe, or just add# type: ignore.cluster.py:bytesis being passed to a formatting string which mypy recommends using!rwith thoseCluster.connect(),self.get_connection_spec()can returnNone, which would causeconn_info.update()to throw an error. Is this desired?Cluster._test_connection()withself._connection_addrReferences #569, #387