Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign upAdd typings #577
Add typings #577
Conversation
|
One thing to note is that the handling of class MyRecord(asyncpg.Record):
id: int
name: str
created_at: datetime.datetime
r: 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] = await conn.fetch('SELECT ... from ...')It's an error because
record: MyRecord = await conn.fetchrow('SELECT ... FROM ...')
records: typing.List[MyRecord] = await conn.fetch('SELECT ... FROM ...')
cursor: asyncpg.cursor.CursorFactory[MyRecord] = await conn.cursor('SELECT ... FROM ...')
record2: asyncpg.Record = await conn.fetchrow('SELECT ... FROM ...')
records2: typing.List[asyncpg.Record] = await conn.fetch('SELECT ... FROM ...')
cursor2: asyncpg.cursor.CursorFactory[asyncpg.Record] = await conn.cursor('SELECT ... FROM ...')
# the variables below have the same types as the last code block,
# but the types are inferred instead
record = await conn.fetch('SELECT ... FROM ...', record_class=MyRecord)
records = await conn.fetch('SELECT ... FROM ...', record_class=MyRecord)
cursor = await conn.cursor('SELECT ... FROM ...', record_class=MyRecord)
record2 = await conn.fetchrow('SELECT ... FROM ...')
records2 = await conn.fetch('SELECT ... FROM ...')
cursor2 = await conn.cursor('SELECT ... FROM ...')Note that while I have the second option coded locally, but I wanted to check if adding that extra |
|
There's a third option: lie about the return type and say it's a |
|
Typing it as |
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 |
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: import asyncpg
import datetime
import typing
import typing_extensions
class MyRecord(asyncpg.Record):
foo: int
bar: typing.Optional[str]
class MyOtherRecord(MyRecord):
baz: datetime.datetime
async def main() -> None:
conn = await asyncpg.connect(...)
m = typing.cast(MyRecord, await conn.fetchrow('SELECT foo, bar FROM records'))
o = typing.cast(MyOtherRecord, await conn.fetchrow('SELECT foo, bar, baz FROM other_records'))
key = 'baz'
fkey: typing_extensions.Final = 'baz'
reveal_type(m['foo']) # int
reveal_type(m['bar']) # Optional[str]
reveal_type(m['baz']) # error: "MyRecord" has no key 'baz'
reveal_type(m[0]) # int
reveal_type(m[1]) # Optional[str]
reveal_type(m[2]) # error: "MyRecord" has no index 2
reveal_type(m.get('foo')) # int
reveal_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']) # int
reveal_type(o['bar']) # Optional[str]
reveal_type(o['baz']) # datetime
reveal_type(o[0]) # int
reveal_type(o[1]) # Optional[str]
reveal_type(o[2]) # datetime
reveal_type(o.get('foo')) # int
reveal_type(o.get('bar')) # Optional[str]
reveal_type(o.get('baz')) # datetime
reveal_type(o.get('baz', 1)) # datetime
reveal_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], await conn.prepare('SELECT ... FROM ...'))
reveal_type(stmt) # asyncpg.prepared_stmt.PreparedStatement[MyRecord]
stmt = await conn.prepare('SELECT ... FROM ...', return_type=MyRecord)
reveal_type(stmt) # asyncpg.prepared_stmt.PreparedStatement[MyRecord]There's also a possible third option if the stmt = await conn.prepare('SELECT ... FROM ...')
reveal_type(stmt) # asyncpg.prepared_stmt.PreparedStatement[asyncpg.protocol.protocol.Record]
record = await stmt.fetchrow(...)
reveal_type(record) # Optional[asyncpg.protocol.protocol.Record]
assert isinstance(record, MyRecord)
reveal_type(record) # MyRecordThe third option completely depends on whether the |
|
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 |
|
@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.
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] |
elprans
Jul 19, 2020
Member
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.
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.
bryanforbes
Jul 19, 2020
Author
I've updated the PR to switch to 3.6 type annotations and removed 3.5 from CI
I've updated the PR to switch to 3.6 type annotations and removed 3.5 from CI
| # Put the connection into the aborted state. | ||
| self._aborted = True | ||
| self._protocol.abort() | ||
| self._protocol = None | ||
| self._protocol = None # type: ignore[assignment] |
elprans
Jul 19, 2020
Member
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.
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
da85005
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.
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.
|
@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! |
|
@victoraugustolls I'll rebase and update this PR today |
4fef52f
to
8332c03
|
@victoraugustolls I finished the rebase, but there's an issue with Python 3.6 and |
|
Thanks for the update @bryanforbes ! I will try and search for something that can help |
|
@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. |
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