Skip to content

Commit 34c0513

Browse files
committed
Implemented Endpoint paginator methods, ResponseParser helper class, and refactored code smells.
1 parent c264e50 commit 34c0513

19 files changed

+233
-224
lines changed

open_sea_v1/endpoints/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
Exposes classes and objects meant to be used by You!
33
Import other modules at your own risk, as their location may change.
44
"""
5+
from open_sea_v1.endpoints.endpoint_client import _ClientParams as ClientParams
6+
57
from open_sea_v1.endpoints.endpoint_assets import _AssetsEndpoint as AssetsEndpoint
68
from open_sea_v1.endpoints.endpoint_assets import _AssetsOrderBy as AssetsOrderBy
79

810
from open_sea_v1.endpoints.endpoint_events import _EventsEndpoint as EventsEndpoint
911
from open_sea_v1.endpoints.endpoint_events import AuctionType
1012
from open_sea_v1.endpoints.endpoint_events import EventType
13+
Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
from abc import ABC, abstractmethod
2-
from typing import Optional, Union
2+
from typing import Optional, Union, Generator
33

44
from requests import Response
55

6-
from open_sea_v1.responses.response__base import _OpenSeaAPIResponse
6+
from open_sea_v1.endpoints.endpoint_client import _ClientParams
7+
from open_sea_v1.responses.response_abc import _OpenSeaResponse
78

89

910
class BaseOpenSeaEndpoint(ABC):
@@ -15,8 +16,8 @@ def __post_init__(self):
1516

1617
@property
1718
@abstractmethod
18-
def api_key(self) -> Optional[str]:
19-
"""Optional OpenSea API key"""
19+
def client_params(self) -> _ClientParams:
20+
"""Instance of common OpenSea Endpoint parameters."""
2021

2122
@property
2223
@abstractmethod
@@ -25,25 +26,19 @@ def url(self) -> str:
2526

2627
@property
2728
@abstractmethod
28-
def _request_params(self) -> dict:
29-
"""Dictionnary of _request_params to pass into the _get_request."""
29+
def get_pages(self) -> Generator[list[list[_OpenSeaResponse]], None, None]:
30+
"""Returns all pages for the query."""
3031

3132
@property
3233
@abstractmethod
33-
def _validate_request_params(self) -> None:
34-
""""""
35-
36-
@property
37-
@abstractmethod
38-
def response(self) -> Union[list[_OpenSeaAPIResponse], _OpenSeaAPIResponse]:
34+
def parsed_http_response(self) -> Union[list[_OpenSeaResponse], _OpenSeaResponse]:
3935
"""Parsed JSON dictionnary from HTTP Response."""
4036

41-
@property
4237
@abstractmethod
43-
def http_response(self) -> Optional[Response]:
44-
"""HTTP Response from Opensea API."""
38+
def _get_request(self) -> Response:
39+
"""Returns HTTP parsed_http_response from OpenSea."""
4540

41+
@property
4642
@abstractmethod
47-
def get_request(self, url: str, method: str = 'GET', **kwargs) -> Response:
48-
"""Call to super()._get_request passing url and _request_params."""
49-
43+
def _validate_request_params(self) -> None:
44+
""""""

open_sea_v1/endpoints/endpoint_assets.py

Lines changed: 28 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
from dataclasses import dataclass
22
from enum import Enum
3-
from typing import Optional
3+
from typing import Optional, Generator
44

55
from open_sea_v1.endpoints.endpoint_abc import BaseOpenSeaEndpoint
6-
from open_sea_v1.endpoints.endpoint_client import OpenSeaClient
6+
from open_sea_v1.endpoints.endpoint_client import BaseOpenSeaClient, _ClientParams
77
from open_sea_v1.endpoints.endpoint_urls import OpenseaApiEndpoints
8-
from open_sea_v1.responses.response_asset import _AssetResponse
8+
from open_sea_v1.responses import AssetResponse
99

1010

1111
class _AssetsOrderBy(str, Enum):
@@ -20,12 +20,14 @@ class _AssetsOrderBy(str, Enum):
2020

2121

2222
@dataclass
23-
class _AssetsEndpoint(OpenSeaClient, BaseOpenSeaEndpoint):
23+
class _AssetsEndpoint(BaseOpenSeaClient, BaseOpenSeaEndpoint):
2424
"""
2525
Opensea API Assets Endpoint
2626
2727
Parameters
2828
----------
29+
client_params:
30+
Common endpoint params.
2931
3032
owner:
3133
The address of the owner of the assets
@@ -45,53 +47,48 @@ class _AssetsEndpoint(OpenSeaClient, BaseOpenSeaEndpoint):
4547
order_direction:
4648
Can be asc for ascending or desc for descending
4749
48-
offset:
49-
Offset
50-
51-
limit:
52-
Defaults to 20, capped at 50.
53-
5450
collection:
5551
Limit responses to members of a collection. Case sensitive and must match the collection slug exactly. Will return all assets from all contracts in a collection.
5652
5753
:return: Parsed JSON
5854
"""
59-
api_key: Optional[str] = None
60-
owner: Optional[str] = None
61-
token_ids: Optional[list[int]] = None
55+
client_params: _ClientParams = None
6256
asset_contract_address: Optional[list[str]] = None
6357
asset_contract_addresses: Optional[str] = None
58+
token_ids: Optional[list[int]] = None
6459
collection: Optional[str] = None
60+
owner: Optional[str] = None
6561
order_by: Optional[_AssetsOrderBy] = None
6662
order_direction: str = None
67-
offset: int = 0
68-
limit: int = 20
6963

7064
def __post_init__(self):
7165
self._validate_request_params()
7266

73-
@property
74-
def response(self) -> list[_AssetResponse]:
75-
self._assert_get_request_was_called_before_accessing_this_property()
76-
assets_json = self._response.json()['assets']
77-
assets = [_AssetResponse(asset_json) for asset_json in assets_json]
78-
return assets
79-
8067
@property
8168
def url(self):
8269
return OpenseaApiEndpoints.ASSETS.value
8370

84-
def get_request(self, *args, **kwargs):
85-
self._response = self._get_request(self.url, **self._request_params)
86-
8771
@property
88-
def _request_params(self) -> dict[dict]:
72+
def parsed_http_response(self) -> list[AssetResponse]:
73+
assets_json = self._http_response.json()['assets']
74+
assets = [AssetResponse(asset_json) for asset_json in assets_json]
75+
return assets
76+
77+
def _get_request(self, **kwargs):
8978
params = dict(
90-
owner=self.owner, token_ids=self.token_ids, asset_contract_address=self.asset_contract_address,
91-
asset_contract_addresses=self.asset_contract_addresses, collection=self.collection,
92-
order_by=self.order_by, order_direction=self.order_direction, offset=self.offset, limit=self.limit
79+
owner=self.owner,
80+
token_ids=self.token_ids,
81+
asset_contract_address=self.asset_contract_address,
82+
asset_contract_addresses=self.asset_contract_addresses,
83+
collection=self.collection,
84+
order_by=self.order_by,
85+
order_direction=self.order_direction,
86+
offset=self.client_params.offset,
87+
limit=self.client_params.limit,
9388
)
94-
return dict(api_key=self.api_key, params=params)
89+
get_request_kwargs = dict(params=params)
90+
self._http_response = super()._get_request(**get_request_kwargs)
91+
return self._http_response
9592

9693
def _validate_request_params(self) -> None:
9794
self._validate_mandatory_params()
@@ -139,5 +136,5 @@ def _validate_order_by(self) -> None:
139136
)
140137

141138
def _validate_limit(self):
142-
if not isinstance(self.limit, int) or not 0 <= self.limit <= 50:
139+
if not isinstance(self.client_params.limit, int) or not 0 <= self.client_params.limit <= 50:
143140
raise ValueError(f"limit param must be an int between 0 and 50.")
Lines changed: 42 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,52 @@
1-
from typing import Optional
1+
from abc import ABC
2+
from dataclasses import dataclass
3+
from typing import Optional, Generator
24

35
from requests import Response, request
46

7+
from open_sea_v1.responses import OpenSeaAPIResponse
58

6-
class OpenSeaClient:
9+
@dataclass
10+
class _ClientParams:
11+
"""Common OpenSea Endpoint parameters to pass in."""
12+
offset: int = 0
13+
limit: int = 20
14+
max_pages: Optional[int] = None
15+
api_key: Optional[str] = None
716

8-
api_key = None
9-
_response: Optional[Response] = None
17+
18+
class BaseOpenSeaClient(ABC):
19+
client_params: _ClientParams
20+
processed_pages: int = 0
21+
response = None
22+
parsed_http_response = None
23+
url = None
24+
_http_response = None
1025

1126
@property
1227
def http_headers(self) -> dict:
13-
return {
14-
"headers":
15-
{"X-API-Key" : self.api_key} if self.api_key else dict(),
16-
}
28+
params = {'headers': dict()}
29+
if self.client_params.api_key:
30+
params['headers'] = {'X-API-Key': self.client_params.api_key}
31+
return params
1732

18-
def _get_request(self, url: str, method: str = 'GET', **kwargs) -> Response:
19-
"""
20-
Automatically passes in API key in HTTP _get_request headers.
21-
"""
22-
if 'api_key' in kwargs:
23-
self.api_key = kwargs.pop('api_key')
33+
def _get_request(self, **kwargs) -> Response:
2434
updated_kwargs = kwargs | self.http_headers
25-
return request(method, url, **updated_kwargs)
26-
27-
@property
28-
def http_response(self):
29-
self._assert_get_request_was_called_before_accessing_this_property()
30-
return self._response
31-
32-
def _assert_get_request_was_called_before_accessing_this_property(self):
33-
if self._response is None:
34-
raise AttributeError('You must call self.request prior to accessing self.response')
35-
36-
# def collections(self, *, asset_owner: Optional[str] = None, offset: int, limit: int) -> OpenseaCollections:
37-
# """
38-
# Use this endpoint to fetch collections and dapps that OpenSea shows on opensea.io,
39-
# along with dapps and smart contracts that a particular user cares about.
40-
#
41-
# :param asset_owner: A wallet address. If specified, will return collections where
42-
# the owner owns at least one asset belonging to smart contracts in the collection.
43-
# The number of assets the account owns is shown as owned_asset_count for each collection.
44-
# :param offset: For pagination. Number of contracts offset from the beginning of the result list.
45-
# :param limit: For pagination. Maximum number of contracts to return.
46-
# :return: Parsed JSON
47-
# """
48-
# if offset != 0:
49-
# raise NotImplementedError(
50-
# "Sorry, tested offset parameter is not implemented yet. "
51-
# "Feel free to PR after looking at the tests and trying to understand"
52-
# " why current implementation doesn't allow pagination to work..."
53-
# )
54-
# resp = self._collections(asset_owner=asset_owner, offset=offset, limit=limit)
55-
# return resp.json()['collections']
56-
#
57-
# def _collections(self, **_request_params) -> Response:
58-
# """Returns HTTPResponse object."""
59-
# url = OpenseaApiEndpoints.COLLECTIONS.value
60-
# return self._get_request("GET", url, _request_params=_request_params)
61-
#
62-
# def asset(self, asset_contract_address: str, token_id: str, account_address: Optional[str] = None) -> OpenseaAsset:
63-
# """
64-
# :param asset_contract_address: Address of the contract for this NFT
65-
# :param token_id: Token ID for this item
66-
# :param account_address: Address of an owner of the token. If you include this, the http_response will include an ownership object that includes the number of tokens owned by the address provided instead of the top_ownerships object included in the standard http_response, which provides the number of tokens owned by each of the 10 addresses with the greatest supply of the token.
67-
# :return: Parsed JSON.
68-
# """
69-
# resp = self._asset(
70-
# asset_contract_address=asset_contract_address,
71-
# token_id=token_id,
72-
# account_address=account_address,
73-
# )
74-
# return resp.response()
75-
35+
return request('GET', self.url, **updated_kwargs)
36+
37+
def get_pages(self) -> Generator[list[list[OpenSeaAPIResponse]], None, None]:
38+
self._http_response = None
39+
while self.remaining_pages():
40+
self._http_response = self._get_request()
41+
yield self.parsed_http_response
42+
self.client_params.offset += self.client_params.limit
43+
self.processed_pages += 1
44+
45+
def remaining_pages(self) -> bool:
46+
if self._http_response is None:
47+
return True
48+
if self.client_params.max_pages is not None and self.processed_pages <= self.client_params.max_pages:
49+
return True
50+
if len(self.response) >= self.client_params.offset:
51+
return True
52+
return False

0 commit comments

Comments
 (0)