Skip to content

Commit b308816

Browse files
authored
proper type annotation (#409)
* remove cast if not neccesary * remove _from_dict because it is not neccesary, using pydantic methods instead * Q_co * extra ignore * version * using cast * version * version * coderabbit comments
1 parent b602497 commit b308816

31 files changed

+134
-154
lines changed

cuenca/__init__.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,6 @@
4444
'get_balance',
4545
]
4646

47-
from typing import cast
48-
4947
from . import http
5048
from .resources import (
5149
Account,
@@ -96,5 +94,5 @@
9694

9795

9896
def get_balance(session: http.Session = session) -> int:
99-
balance_entry = cast('BalanceEntry', BalanceEntry.first(session=session))
97+
balance_entry = BalanceEntry.first(session=session)
10098
return balance_entry.rolling_balance if balance_entry else 0

cuenca/resources/api_keys.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import datetime as dt
2-
from typing import ClassVar, Optional, cast
2+
from typing import ClassVar, Optional
33

44
from cuenca_validations.types import ApiKeyQuery, ApiKeyUpdateRequest
55

@@ -36,7 +36,7 @@ def active(self) -> bool:
3636

3737
@classmethod
3838
def create(cls, *, session: Session = global_session) -> 'ApiKey':
39-
return cast('ApiKey', cls._create(session=session))
39+
return cls._create(session=session)
4040

4141
@classmethod
4242
def deactivate(
@@ -55,7 +55,7 @@ def deactivate(
5555
"""
5656
url = cls._resource + f'/{api_key_id}'
5757
resp = session.delete(url, dict(minutes=minutes))
58-
return cast('ApiKey', cls._from_dict(resp))
58+
return cls(**resp)
5959

6060
@classmethod
6161
def update(
@@ -74,5 +74,4 @@ def update(
7474
req = ApiKeyUpdateRequest(
7575
metadata=metadata, user_id=user_id, platform_id=platform_id
7676
)
77-
resp = cls._update(api_key_id, **req.dict(), session=session)
78-
return cast('ApiKey', resp)
77+
return cls._update(api_key_id, **req.dict(), session=session)

cuenca/resources/arpc.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import datetime as dt
2-
from typing import ClassVar, Optional, cast
2+
from typing import ClassVar, Optional
33

44
from cuenca_validations.types.requests import ARPCRequest
55

@@ -52,4 +52,4 @@ def create(
5252
unique_number=unique_number,
5353
track_data_method=track_data_method,
5454
)
55-
return cast('Arpc', cls._create(session=session, **req.dict()))
55+
return cls._create(session=session, **req.dict())

cuenca/resources/base.py

Lines changed: 66 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import datetime as dt
33
import json
44
from io import BytesIO
5-
from typing import ClassVar, Dict, Generator, Optional, Union
5+
from typing import Any, ClassVar, Generator, Optional, Type, TypeVar, cast
66
from urllib.parse import urlencode
77

88
from cuenca_validations.types import (
@@ -12,34 +12,21 @@
1212
TransactionQuery,
1313
TransactionStatus,
1414
)
15-
from pydantic import BaseModel
15+
from pydantic import BaseModel, Extra
1616

1717
from ..exc import MultipleResultsFound, NoResultFound
1818
from ..http import Session, session as global_session
1919

20+
R_co = TypeVar('R_co', bound='Resource', covariant=True)
21+
2022

2123
class Resource(BaseModel):
2224
_resource: ClassVar[str]
2325

2426
id: str
2527

26-
@classmethod
27-
def _from_dict(cls, obj_dict: Dict[str, Union[str, int]]) -> 'Resource':
28-
cls._filter_excess_fields(obj_dict)
29-
return cls(**obj_dict)
30-
31-
@classmethod
32-
def _filter_excess_fields(cls, obj_dict):
33-
"""
34-
dataclasses don't allow __init__ to be called with excess fields. This
35-
method allows the API to add fields in the response body without
36-
breaking the client
37-
"""
38-
excess = set(obj_dict.keys()) - set(
39-
cls.schema().get("properties").keys()
40-
)
41-
for f in excess:
42-
del obj_dict[f]
28+
class Config:
29+
extra = Extra.ignore
4330

4431
def to_dict(self):
4532
return SantizedDict(self.dict())
@@ -48,22 +35,30 @@ def to_dict(self):
4835
class Retrievable(Resource):
4936
@classmethod
5037
def retrieve(
51-
cls, id: str, *, session: Session = global_session
52-
) -> Resource:
38+
cls: Type[R_co],
39+
id: str,
40+
*,
41+
session: Session = global_session,
42+
) -> R_co:
5343
resp = session.get(f'/{cls._resource}/{id}')
54-
return cls._from_dict(resp)
44+
return cls(**resp)
5545

56-
def refresh(self, *, session: Session = global_session):
46+
def refresh(self, *, session: Session = global_session) -> None:
5747
new = self.retrieve(self.id, session=session)
5848
for attr, value in new.__dict__.items():
5949
setattr(self, attr, value)
6050

6151

6252
class Creatable(Resource):
6353
@classmethod
64-
def _create(cls, *, session: Session = global_session, **data) -> Resource:
54+
def _create(
55+
cls: Type[R_co],
56+
*,
57+
session: Session = global_session,
58+
**data: Any,
59+
) -> R_co:
6560
resp = session.post(cls._resource, data)
66-
return cls._from_dict(resp)
61+
return cls(**resp)
6762

6863

6964
class Updateable(Resource):
@@ -72,31 +67,39 @@ class Updateable(Resource):
7267

7368
@classmethod
7469
def _update(
75-
cls, id: str, *, session: Session = global_session, **data
76-
) -> Resource:
70+
cls: Type[R_co],
71+
id: str,
72+
*,
73+
session: Session = global_session,
74+
**data: Any,
75+
) -> R_co:
7776
resp = session.patch(f'/{cls._resource}/{id}', data)
78-
return cls._from_dict(resp)
77+
return cls(**resp)
7978

8079

8180
class Deactivable(Resource):
8281
deactivated_at: Optional[dt.datetime]
8382

8483
@classmethod
8584
def deactivate(
86-
cls, id: str, *, session: Session = global_session, **data
87-
) -> Resource:
85+
cls: Type[R_co],
86+
id: str,
87+
*,
88+
session: Session = global_session,
89+
**data: Any,
90+
) -> R_co:
8891
resp = session.delete(f'/{cls._resource}/{id}', data)
89-
return cls._from_dict(resp)
92+
return cls(**resp)
9093

9194
@property
92-
def is_active(self):
95+
def is_active(self) -> bool:
9396
return not self.deactivated_at
9497

9598

9699
class Downloadable(Resource):
97100
@classmethod
98101
def download(
99-
cls,
102+
cls: Type[R_co],
100103
id: str,
101104
file_format: FileFormat = FileFormat.any,
102105
*,
@@ -121,13 +124,13 @@ def xml(self) -> bytes:
121124
class Uploadable(Resource):
122125
@classmethod
123126
def _upload(
124-
cls,
127+
cls: Type[R_co],
125128
file: bytes,
126129
user_id: str,
127130
*,
128131
session: Session = global_session,
129-
**data,
130-
) -> Resource:
132+
**data: Any,
133+
) -> R_co:
131134
encoded_file = base64.b64encode(file)
132135
resp = session.request(
133136
'post',
@@ -138,7 +141,7 @@ def _upload(
138141
**{k: (None, v) for k, v in data.items()},
139142
),
140143
)
141-
return cls._from_dict(json.loads(resp))
144+
return cls(**json.loads(resp))
142145

143146

144147
class Queryable(Resource):
@@ -148,50 +151,62 @@ class Queryable(Resource):
148151

149152
@classmethod
150153
def one(
151-
cls, *, session: Session = global_session, **query_params
152-
) -> Resource:
153-
q = cls._query_params(limit=2, **query_params)
154+
cls: Type[R_co],
155+
*,
156+
session: Session = global_session,
157+
**query_params: Any,
158+
) -> R_co:
159+
q = cast(Queryable, cls)._query_params(limit=2, **query_params)
154160
resp = session.get(cls._resource, q.dict())
155161
items = resp['items']
156162
len_items = len(items)
157163
if not len_items:
158164
raise NoResultFound
159165
if len_items > 1:
160166
raise MultipleResultsFound
161-
return cls._from_dict(items[0])
167+
return cls(**items[0])
162168

163169
@classmethod
164170
def first(
165-
cls, *, session: Session = global_session, **query_params
166-
) -> Optional[Resource]:
167-
q = cls._query_params(limit=1, **query_params)
171+
cls: Type[R_co],
172+
*,
173+
session: Session = global_session,
174+
**query_params: Any,
175+
) -> Optional[R_co]:
176+
q = cast(Queryable, cls)._query_params(limit=1, **query_params)
168177
resp = session.get(cls._resource, q.dict())
169178
try:
170179
item = resp['items'][0]
171180
except IndexError:
172181
rv = None
173182
else:
174-
rv = cls._from_dict(item)
183+
rv = cls(**item)
175184
return rv
176185

177186
@classmethod
178187
def count(
179-
cls, *, session: Session = global_session, **query_params
188+
cls: Type[R_co],
189+
*,
190+
session: Session = global_session,
191+
**query_params: Any,
180192
) -> int:
181-
q = cls._query_params(count=True, **query_params)
193+
q = cast(Queryable, cls)._query_params(count=True, **query_params)
182194
resp = session.get(cls._resource, q.dict())
183195
return resp['count']
184196

185197
@classmethod
186198
def all(
187-
cls, *, session: Session = global_session, **query_params
188-
) -> Generator[Resource, None, None]:
199+
cls: Type[R_co],
200+
*,
201+
session: Session = global_session,
202+
**query_params: Any,
203+
) -> Generator[R_co, None, None]:
189204
session = session or global_session
190-
q = cls._query_params(**query_params)
205+
q = cast(Queryable, cls)._query_params(**query_params)
191206
next_page_uri = f'{cls._resource}?{urlencode(q.dict())}'
192207
while next_page_uri:
193208
page = session.get(next_page_uri)
194-
yield from (cls._from_dict(item) for item in page['items'])
209+
yield from (cls(**item) for item in page['items'])
195210
next_page_uri = page['next_page_uri']
196211

197212

cuenca/resources/card_activations.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,7 @@ def create(
4242
exp_year=exp_year,
4343
cvv2=cvv2,
4444
)
45-
return cast(
46-
'CardActivation', cls._create(session=session, **req.dict())
47-
)
45+
return cls._create(session=session, **req.dict())
4846

4947
@property
5048
def card(self) -> Optional[Card]:

cuenca/resources/card_validations.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,7 @@ def create(
5151
pin_block=pin_block,
5252
pin_attempts_exceeded=pin_attempts_exceeded,
5353
)
54-
return cast(
55-
'CardValidation', cls._create(session=session, **req.dict())
56-
)
54+
return cls._create(session=session, **req.dict())
5755

5856
@property
5957
def card(self) -> Card:

cuenca/resources/cards.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import datetime as dt
2-
from typing import ClassVar, Optional, cast
2+
from typing import ClassVar, Optional
33

44
from cuenca_validations.types import (
55
CardFundingType,
@@ -81,7 +81,7 @@ def create(
8181
card_holder_user_id=card_holder_user_id,
8282
is_dynamic_cvv=is_dynamic_cvv,
8383
)
84-
return cast('Card', cls._create(session=session, **req.dict()))
84+
return cls._create(session=session, **req.dict())
8585

8686
@classmethod
8787
def update(
@@ -106,8 +106,7 @@ def update(
106106
req = CardUpdateRequest(
107107
status=status, pin_block=pin_block, is_dynamic_cvv=is_dynamic_cvv
108108
)
109-
resp = cls._update(card_id, session=session, **req.dict())
110-
return cast('Card', resp)
109+
return cls._update(card_id, session=session, **req.dict())
111110

112111
@classmethod
113112
def deactivate(
@@ -118,4 +117,4 @@ def deactivate(
118117
"""
119118
url = f'{cls._resource}/{card_id}'
120119
resp = session.delete(url)
121-
return cast('Card', cls._from_dict(resp))
120+
return cls(**resp)

cuenca/resources/clabes.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import ClassVar, cast
1+
from typing import ClassVar
22

33
from ..http import Session, session as global_session
44
from .base import Creatable, Queryable, Retrievable
@@ -11,4 +11,4 @@ class Clabe(Creatable, Queryable, Retrievable):
1111

1212
@classmethod
1313
def create(cls, session: Session = global_session):
14-
return cast('Clabe', cls._create(session=session))
14+
return cls._create(session=session)

cuenca/resources/curp_validations.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import datetime as dt
2-
from typing import ClassVar, Optional, cast
2+
from typing import ClassVar, Optional
33

44
from cuenca_validations.types import (
55
Country,
@@ -98,7 +98,4 @@ def create(
9898
gender=gender,
9999
manual_curp=manual_curp,
100100
)
101-
return cast(
102-
'CurpValidation',
103-
cls._create(session=session, **req.dict()),
104-
)
101+
return cls._create(session=session, **req.dict())

0 commit comments

Comments
 (0)