Skip to content

Commit

Permalink
Implemented EventsEndpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
dehidehidehi committed Aug 3, 2021
1 parent d4e0d26 commit 7887454
Show file tree
Hide file tree
Showing 21 changed files with 554 additions and 73 deletions.
6 changes: 5 additions & 1 deletion open_sea_v1/endpoints/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,8 @@
Import other modules at your own risk, as their location may change.
"""
from open_sea_v1.endpoints.endpoint_assets import _AssetsEndpoint as AssetsEndpoint
from open_sea_v1.endpoints.endpoint_assets import _AssetsOrderBy as AssetsOrderBy
from open_sea_v1.endpoints.endpoint_assets import _AssetsOrderBy as AssetsOrderBy

from open_sea_v1.endpoints.endpoint_events import _EventsEndpoint as EventsEndpoint
from open_sea_v1.endpoints.endpoint_events import AuctionType
from open_sea_v1.endpoints.endpoint_events import EventType
25 changes: 19 additions & 6 deletions open_sea_v1/endpoints/endpoint_abc.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
from abc import ABC, abstractmethod
from typing import Optional
from typing import Optional, Union

from requests import Response

from open_sea_v1.responses.response__base import _OpenSeaAPIResponse


class BaseOpenSeaEndpoint(ABC):

@property
@abstractmethod
def get_request(self, url: str, method: str = 'GET', **kwargs) -> Response:
"""Call to super().get_request passing url and _request_params."""
def __post_init__(self):
"""Using post_init to run param validation"""

@property
@abstractmethod
Expand All @@ -23,14 +26,24 @@ def url(self) -> str:
@property
@abstractmethod
def _request_params(self) -> dict:
"""Dictionnary of _request_params to pass into the get_request."""
"""Dictionnary of _request_params to pass into the _get_request."""

@property
@abstractmethod
def validate_request_params(self) -> None:
def _validate_request_params(self) -> None:
""""""

@property
@abstractmethod
def response(self) -> list[dict]:
def response(self) -> Union[list[_OpenSeaAPIResponse], _OpenSeaAPIResponse]:
"""Parsed JSON dictionnary from HTTP Response."""

@property
@abstractmethod
def http_response(self) -> Optional[Response]:
"""HTTP Response from Opensea API."""

@abstractmethod
def get_request(self, url: str, method: str = 'GET', **kwargs) -> Response:
"""Call to super()._get_request passing url and _request_params."""

30 changes: 8 additions & 22 deletions open_sea_v1/endpoints/endpoint_assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@
from enum import Enum
from typing import Optional

from requests import Response

from open_sea_v1.endpoints.endpoint_urls import OpenseaApiEndpoints
from open_sea_v1.endpoints.endpoint_client import OpenSeaClient
from open_sea_v1.endpoints.endpoint_abc import BaseOpenSeaEndpoint
from open_sea_v1.responses.asset_obj import _AssetResponse
from open_sea_v1.endpoints.endpoint_client import OpenSeaClient
from open_sea_v1.endpoints.endpoint_urls import OpenseaApiEndpoints
from open_sea_v1.responses.response_asset import _AssetResponse


class _AssetsOrderBy(str, Enum):
Expand All @@ -28,8 +26,6 @@ class _AssetsEndpoint(OpenSeaClient, BaseOpenSeaEndpoint):
Parameters
----------
width:
width of the snake
owner:
The address of the owner of the assets
Expand Down Expand Up @@ -72,17 +68,11 @@ class _AssetsEndpoint(OpenSeaClient, BaseOpenSeaEndpoint):
limit: int = 20

def __post_init__(self):
self.validate_request_params()
self._response: Optional[Response] = None

@property
def http_response(self):
self._validate_response_property()
return self._response
self._validate_request_params()

@property
def response(self) -> list[_AssetResponse]:
self._validate_response_property()
self._assert_get_request_was_called_before_accessing_this_property()
assets_json = self._response.json()['assets']
assets = [_AssetResponse(asset_json) for asset_json in assets_json]
return assets
Expand All @@ -92,7 +82,7 @@ def url(self):
return OpenseaApiEndpoints.ASSETS.value

def get_request(self, *args, **kwargs):
self._response = super().get_request(self.url, **self._request_params)
self._response = self._get_request(self.url, **self._request_params)

@property
def _request_params(self) -> dict[dict]:
Expand All @@ -103,17 +93,13 @@ def _request_params(self) -> dict[dict]:
)
return dict(api_key=self.api_key, params=params)

def validate_request_params(self) -> None:
def _validate_request_params(self) -> None:
self._validate_mandatory_params()
self._validate_asset_contract_addresses()
self._validate_order_direction()
self._validate_order_by()
self._validate_limit()

def _validate_response_property(self):
if self._response is None:
raise AttributeError('You must call self.request prior to accessing self.response')

def _validate_mandatory_params(self):
mandatory = self.owner, self.token_ids, self.asset_contract_address, self.asset_contract_addresses, self.collection
if all((a is None for a in mandatory)):
Expand All @@ -123,7 +109,7 @@ def _validate_mandatory_params(self):
def _validate_asset_contract_addresses(self):
if self.asset_contract_address and self.asset_contract_addresses:
raise ValueError(
"You cannot simultaneously get_request for a single contract_address and a list of contract_addresses."
"You cannot simultaneously _get_request for a single contract_address and a list of contract_addresses."
)

if self.token_ids and not (self.asset_contract_address or self.asset_contract_addresses):
Expand Down
18 changes: 15 additions & 3 deletions open_sea_v1/endpoints/endpoint_client.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
from typing import Optional

from requests import Response, request


class OpenSeaClient:

api_key = None
_response: Optional[Response] = None

@property
def http_headers(self) -> dict:
Expand All @@ -12,15 +15,24 @@ def http_headers(self) -> dict:
{"X-API-Key" : self.api_key} if self.api_key else dict(),
}

def get_request(self, url: str, method: str = 'GET', **kwargs) -> Response:
def _get_request(self, url: str, method: str = 'GET', **kwargs) -> Response:
"""
Automatically passes in API key in HTTP get_request headers.
Automatically passes in API key in HTTP _get_request headers.
"""
if 'api_key' in kwargs:
self.api_key = kwargs.pop('api_key')
updated_kwargs = kwargs | self.http_headers
return request(method, url, **updated_kwargs)

@property
def http_response(self):
self._assert_get_request_was_called_before_accessing_this_property()
return self._response

def _assert_get_request_was_called_before_accessing_this_property(self):
if self._response is None:
raise AttributeError('You must call self.request prior to accessing self.response')

# def collections(self, *, asset_owner: Optional[str] = None, offset: int, limit: int) -> OpenseaCollections:
# """
# Use this endpoint to fetch collections and dapps that OpenSea shows on opensea.io,
Expand All @@ -45,7 +57,7 @@ def get_request(self, url: str, method: str = 'GET', **kwargs) -> Response:
# def _collections(self, **_request_params) -> Response:
# """Returns HTTPResponse object."""
# url = OpenseaApiEndpoints.COLLECTIONS.value
# return self.get_request("GET", url, _request_params=_request_params)
# return self._get_request("GET", url, _request_params=_request_params)
#
# def asset(self, asset_contract_address: str, token_id: str, account_address: Optional[str] = None) -> OpenseaAsset:
# """
Expand Down
154 changes: 154 additions & 0 deletions open_sea_v1/endpoints/endpoint_events.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
from dataclasses import dataclass
from datetime import datetime
from typing import Optional

from open_sea_v1.endpoints.endpoint_abc import BaseOpenSeaEndpoint
from open_sea_v1.endpoints.endpoint_client import OpenSeaClient
from open_sea_v1.endpoints.endpoint_urls import OpenseaApiEndpoints
from open_sea_v1.helpers.extended_classes import ExtendedStrEnum
from open_sea_v1.responses import EventResponse


class EventType(ExtendedStrEnum):
"""
The event type to filter. Can be created for new auctions, successful for sales, cancelled, bid_entered, bid_withdrawn, transfer, or approve
"""
CREATED = 'created'
SUCCESSFUL = 'successful'
CANCELLED = 'cancelled'
BID_ENTERED = 'bid_entered'
BID_WITHDRAWN = 'bid_withdrawn'
TRANSFER = 'transfer'
APPROVE = 'approve'


class AuctionType(ExtendedStrEnum):
"""
Filter by an auction type. Can be english for English Auctions, dutch for fixed-price and declining-price sell orders (Dutch Auctions), or min-price for CryptoPunks bidding auctions.
"""
ENGLISH = 'english'
DUTCH = 'dutch'
MIN_PRICE = 'min-price'


@dataclass
class _EventsEndpoint(OpenSeaClient, BaseOpenSeaEndpoint):
"""
Opensea API Events Endpoint
Parameters
----------
offset:
Offset for pagination
limit:
Limit for pagination
asset_contract_address:
The NFT contract address for the assets for which to show events
event_type:
The event type to filter. Can be created for new auctions, successful for sales, cancelled, bid_entered, bid_withdrawn, transfer, or approve
only_opensea:
Restrict to events on OpenSea auctions. Can be true or false
auction_type:
Filter by an auction type. Can be english for English Auctions, dutch for fixed-price and declining-price sell orders (Dutch Auctions), or min-price for CryptoPunks bidding auctions.
occurred_before:
Only show events listed before this datetime.
occurred_after:
Only show events listed after this datetime.
api_key:
Optional Opensea API key, if you have one.
:return: Parsed JSON
"""
offset: int
limit: int
asset_contract_address: str
event_type: EventType
only_opensea: bool
collection_slug: Optional[str] = None
token_id: Optional[str] = None
account_address: Optional[str] = None
auction_type: Optional[AuctionType] = None
occurred_before: Optional[datetime] = None
occurred_after: Optional[datetime] = None
api_key: Optional[str] = None

def __post_init__(self):
self._validate_request_params()

@property
def url(self) -> str:
return OpenseaApiEndpoints.EVENTS.value

@property
def _request_params(self) -> dict:
params = dict(offset=self.offset, limit=self.limit, asset_contract_address=self.asset_contract_address, event_type=self.event_type, only_opensea=self.only_opensea, collection_slug=self.collection_slug, token_id=self.token_id, account_address=self.account_address, auction_type=self.auction_type, occurred_before=self.occurred_before, occurred_after=self.occurred_after)
return dict(api_key=self.api_key, params=params)

def get_request(self, **kwargs):
self._response = self._get_request(self.url, **self._request_params)

@property
def response(self) -> list[EventResponse]:
self._assert_get_request_was_called_before_accessing_this_property()
events_json = self._response.json()['asset_events']
events = [EventResponse(event) for event in events_json]
return events

def _validate_request_params(self) -> None:
self._validate_param_auction_type()
self._validate_param_event_type()
self._validate_params_occurred_before_and_occurred_after()

def _validate_param_event_type(self) -> None:
if not isinstance(self.event_type, (str, EventType)):
raise TypeError('Invalid event_type type. Must be str or EventType Enum.', f"{self.event_type=}")

if self.event_type not in EventType.list():
raise ValueError('Invalid event_type value. Must be str value from EventType Enum.', f"{self.event_type=}")

def _validate_param_auction_type(self) -> None:
if self.auction_type is None:
return

if not isinstance(self.auction_type, (str, AuctionType)):
raise TypeError('Invalid auction_type type. Must be str or AuctionType Enum.', f"{self.auction_type=}")

if self.auction_type not in AuctionType.list():
raise ValueError('Invalid auction_type value. Must be str value from AuctionType Enum.',
f"{self.auction_type=}")

def _validate_params_occurred_before_and_occurred_after(self) -> None:
self._validate_param_occurred_before()
self._validate_param_occurred_after()
if self.occurred_after and self.occurred_before:
self._assert_param_occurred_before_after_cannot_be_same_value()
self._assert_param_occurred_before_cannot_be_higher_than_occurred_after()

def _validate_param_occurred_before(self) -> None:
if not isinstance(self.occurred_before, (type(None), datetime)):
raise TypeError('Invalid occurred_before type. Must be instance of datetime.',
f'{type(self.occurred_before)=}')

def _validate_param_occurred_after(self) -> None:
if not isinstance(self.occurred_after, (type(None), datetime)):
raise TypeError('Invalid occurred_after type. Must be instance of datetime.',
f'{type(self.occurred_after)=}')

def _assert_param_occurred_before_after_cannot_be_same_value(self) -> None:
if self.occurred_after == self.occurred_before:
raise ValueError('Params occurred_after and occurred_before may not have the same value.',
f"{self.occurred_before=}, {self.occurred_after=}")

def _assert_param_occurred_before_cannot_be_higher_than_occurred_after(self) -> None:
if not self.occurred_after < self.occurred_before:
raise ValueError('Param occurred_before cannot be higher than param occurred_after.',
f"{self.occurred_before=}, {self.occurred_after=}")
2 changes: 1 addition & 1 deletion open_sea_v1/endpoints/tests/test_assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from unittest import TestCase

from open_sea_v1.endpoints.endpoint_assets import _AssetsEndpoint, _AssetsOrderBy
from open_sea_v1.responses.asset_obj import _AssetResponse
from open_sea_v1.responses.response_asset import _AssetResponse


class TestAssetsRequest(TestCase):
Expand Down
Loading

0 comments on commit 7887454

Please sign in to comment.