Skip to content

Commit 3ddfd3f

Browse files
committed
complete base conn interface
1 parent 62f7610 commit 3ddfd3f

File tree

3 files changed

+45
-29
lines changed

3 files changed

+45
-29
lines changed

pynamodb/async_util.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import functools
2+
3+
4+
def run_secretly_sync_async_fn(async_fn, *args, **kwargs):
5+
# From https://github.com/python-trio/hip/issues/1#issuecomment-322028457
6+
coro = async_fn(*args, **kwargs)
7+
try:
8+
coro.send(None)
9+
except StopIteration as exc:
10+
return exc.value
11+
else:
12+
raise RuntimeError("you lied, this async function is not secretly synchronous")
13+
14+
15+
def wrap_secretly_sync_async_fn(async_fn):
16+
@functools.wraps(async_fn)
17+
def wrap(*args, **kwargs):
18+
return run_secretly_sync_async_fn(async_fn, *args, **kwargs)
19+
return wrap

pynamodb/connection/base.py

Lines changed: 9 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
Lowest level connection
33
"""
44
import asyncio
5-
import functools
65
import json
76
import logging
87
import random
@@ -28,6 +27,7 @@
2827
from aiobotocore.session import get_session as get_async_session
2928

3029

30+
from pynamodb.async_util import wrap_secretly_sync_async_fn
3131
from pynamodb.constants import (
3232
RETURN_CONSUMED_CAPACITY_VALUES, RETURN_ITEM_COLL_METRICS_VALUES,
3333
RETURN_ITEM_COLL_METRICS, RETURN_CONSUMED_CAPACITY, RETURN_VALUES_VALUES,
@@ -71,17 +71,6 @@
7171
log.addHandler(logging.NullHandler())
7272

7373

74-
def run_secretly_sync_async_fn(async_fn, *args, **kwargs):
75-
# From https://github.com/python-trio/hip/issues/1#issuecomment-322028457
76-
coro = async_fn(*args, **kwargs)
77-
try:
78-
coro.send(None)
79-
except StopIteration as exc:
80-
return exc.value
81-
else:
82-
raise RuntimeError("you lied, this async function is not secretly synchronous")
83-
84-
8574
class MetaTable(object):
8675
"""
8776
A pythonic wrapper around table metadata
@@ -254,20 +243,15 @@ def get_exclusive_start_key_map(self, exclusive_start_key):
254243
}
255244

256245

257-
def sync_wrapper(async_fn):
258-
@functools.wraps(async_fn)
259-
def wrap(*args, **kwargs):
260-
return run_secretly_sync_async_fn(async_fn, *args, **kwargs)
261-
return wrap
262-
263-
264246
class ConnectionMeta(type):
265247
def __init__(self, name, bases, attrs):
266248
super().__init__(name, bases, attrs)
267249

268250
for attr_name, attr_value in attrs.items():
269251
if attr_name.endswith('_async') and asyncio.iscoroutinefunction(attr_value):
270-
setattr(self, attr_name.rstrip("_async"), sync_wrapper(attr_value))
252+
wrapped_fn = wrap_secretly_sync_async_fn(attr_value)
253+
wrapped_fn.__name__ = wrapped_fn.__name__.rstrip('_async')
254+
setattr(self, wrapped_fn.__name__, wrapped_fn)
271255

272256

273257
class Connection(metaclass=ConnectionMeta):
@@ -370,7 +354,7 @@ async def dispatch(self, operation_name, operation_kwargs):
370354

371355
self.send_pre_boto_callback(operation_name, req_uuid, table_name)
372356

373-
data = self._make_api_call(operation_name, operation_kwargs)
357+
data = await self._make_api_call(operation_name, operation_kwargs)
374358
if asyncio.iscoroutine(data):
375359
data = await data
376360

@@ -395,7 +379,7 @@ def send_pre_boto_callback(self, operation_name, req_uuid, table_name):
395379
except Exception as e:
396380
log.exception("pre_boto callback threw an exception.")
397381

398-
def _make_api_call(self, operation_name, operation_kwargs):
382+
async def _make_api_call(self, operation_name, operation_kwargs):
399383
"""
400384
This private method is here for two reasons:
401385
1. It's faster to avoid using botocore's response parsing
@@ -422,7 +406,7 @@ def _make_api_call(self, operation_name, operation_kwargs):
422406
prepared_request.reset_stream()
423407

424408
# Create a new request for each retry (including a new signature).
425-
prepared_request = run_secretly_sync_async_fn(self._create_prepared_request, self.client, request_dict, operation_model)
409+
prepared_request = await self._create_prepared_request(self.client, request_dict, operation_model)
426410

427411
# Implement the before-send event from botocore
428412
event_name = 'before-send.dynamodb.{}'.format(operation_model.name)
@@ -694,7 +678,7 @@ async def create_table_async(
694678
raise TableError("Failed to create table: {}".format(e), e)
695679
return data
696680

697-
def update_time_to_live(self, table_name: str, ttl_attribute_name: str) -> Dict:
681+
async def update_time_to_live_async(self, table_name: str, ttl_attribute_name: str) -> Dict:
698682
"""
699683
Performs the UpdateTimeToLive operation
700684
"""
@@ -706,7 +690,7 @@ def update_time_to_live(self, table_name: str, ttl_attribute_name: str) -> Dict:
706690
}
707691
}
708692
try:
709-
return self.dispatch(UPDATE_TIME_TO_LIVE, operation_kwargs)
693+
return await self.dispatch(UPDATE_TIME_TO_LIVE, operation_kwargs)
710694
except BOTOCORE_EXCEPTIONS as e:
711695
raise TableError("Failed to update TTL on table: {}".format(e), e)
712696

pynamodb/connection/table.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,26 @@
33
~~~~~~~~~~~~~~~~~~~~~~~~~~~
44
"""
55

6+
import asyncio
67
from typing import Any, Dict, Mapping, Optional, Sequence
78

9+
from pynamodb.async_util import wrap_secretly_sync_async_fn
810
from pynamodb.connection.base import Connection, MetaTable
911
from pynamodb.constants import DEFAULT_BILLING_MODE, KEY
1012
from pynamodb.expressions.condition import Condition
1113
from pynamodb.expressions.update import Action
1214

1315

14-
class TableConnection:
16+
class TableMeta(type):
17+
def __init__(self, name, bases, attrs):
18+
super().__init__(name, bases, attrs)
19+
20+
for attr_name, attr_value in attrs.items():
21+
if attr_name.endswith('_async') and asyncio.iscoroutinefunction(attr_value):
22+
setattr(self, attr_name.rstrip("_async"), wrap_secretly_sync_async_fn(attr_value))
23+
24+
25+
class TableConnection(metaclass=TableMeta):
1526
"""
1627
A higher level abstraction over botocore
1728
"""
@@ -32,6 +43,8 @@ def __init__(
3243
aws_session_token: Optional[str] = None,
3344
) -> None:
3445
self.table_name = table_name
46+
47+
# TODO: optional async
3548
self.connection = Connection(region=region,
3649
host=host,
3750
connect_timeout_seconds=connect_timeout_seconds,
@@ -46,7 +59,7 @@ def __init__(
4659
aws_secret_access_key,
4760
aws_session_token)
4861

49-
def get_meta_table(self, refresh: bool = False) -> MetaTable:
62+
def get_meta_table_async(self, refresh: bool = False) -> MetaTable:
5063
"""
5164
Returns a MetaTable
5265
"""
@@ -259,11 +272,11 @@ def query(
259272
scan_index_forward=scan_index_forward,
260273
select=select)
261274

262-
def describe_table(self) -> Dict:
275+
async def describe_table_async(self) -> Dict:
263276
"""
264277
Performs the DescribeTable operation and returns the result
265278
"""
266-
return self.connection.describe_table(self.table_name)
279+
return await self.connection.describe_table_async(self.table_name)
267280

268281
def delete_table(self) -> Dict:
269282
"""

0 commit comments

Comments
 (0)