Uh oh!
There was an error while loading. Please reload this page.
Add typings - #577
Conversation
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. |
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.
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!
| low, high = port_range | ||
| port = low | ||
| port = low # type: typing.Optional[int] |
There was a problem hiding this comment.
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.
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.
| self._aborted = True | ||
| self._protocol.abort() | ||
| self._protocol = None | ||
| self._protocol = None # type: ignore[assignment] |
There was a problem hiding this comment.
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 |
@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! |
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. |
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