Skip to content

Commit

Permalink
make everything async
Browse files Browse the repository at this point in the history
  • Loading branch information
owen-zora committed Sep 21, 2023
1 parent 93f72b0 commit 7a16820
Show file tree
Hide file tree
Showing 11 changed files with 382 additions and 40 deletions.
2 changes: 1 addition & 1 deletion offchain/metadata/parsers/collection/hashmasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def parse_metadata(self, token: Token, raw_data: dict, *args, **kwargs) -> Optio
additional_fields=self.get_additional_fields(raw_data=raw_data),
)

async def gen_parse_metadata(self, token: Token, raw_data: dict, *args, **kwargs) -> Optional[Metadata]: # type: ignore[no-untyped-def, type-arg] # noqa: E501
async def _gen_parse_metadata_impl(self, token: Token, raw_data: dict, *args, **kwargs) -> Optional[Metadata]: # type: ignore[no-untyped-def, type-arg] # noqa: E501
token.uri = f"https://hashmap.azurewebsites.net/getMask/{token.token_id}"

raw_data, mime_type_and_size, name, image = await asyncio.gather(
Expand Down
70 changes: 67 additions & 3 deletions offchain/metadata/parsers/collection/punks.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import asyncio
from typing import Optional
from urllib.parse import quote

from offchain.constants.addresses import CollectionAddress
from offchain.metadata.models.metadata import (
Metadata,
MediaDetails,
Attribute,
MediaDetails,
Metadata,
MetadataField,
MetadataFieldType,
)
from offchain.metadata.models.token import Token
from offchain.metadata.parsers.collection.collection_parser import CollectionParser
from offchain.metadata.registries.parser_registry import ParserRegistry
from urllib.parse import quote


@ParserRegistry.register
Expand All @@ -36,13 +37,33 @@ def make_call(self, index: int, function_sig: str) -> Optional[str]:

return results[0]

async def gen_call(self, index: int, function_sig: str) -> Optional[str]:
results = await self.contract_caller.rpc.async_reader.gen_call_single_function_single_address_many_args(
address=CollectionAddress.PUNKS_DATA,
function_sig=function_sig,
return_type=["string"],
args=[[index]],
)

if len(results) < 1:
return None

return results[0]

def get_image(self, index: int) -> Optional[MediaDetails]:
raw_uri = self.make_call(index, "punkImageSvg(uint16)")
image_uri = self.encode_uri_data(raw_uri) # type: ignore[arg-type]
return MediaDetails(
uri=image_uri, size=None, sha256=None, mime_type="image/svg+xml"
) # noqa: E501

async def gen_image(self, index: int) -> Optional[MediaDetails]:
raw_uri = await self.gen_call(index, "punkImageSvg(uint16)")
image_uri = self.encode_uri_data(raw_uri) # type: ignore[arg-type]
return MediaDetails(
uri=image_uri, size=None, sha256=None, mime_type="image/svg+xml"
) # noqa: E501

def parse_additional_fields(self, raw_data: dict) -> list[MetadataField]: # type: ignore[type-arg] # noqa: E501
additional_fields = []
if (external_url := raw_data.get("external_url")) is not None:
Expand Down Expand Up @@ -88,6 +109,28 @@ def parse_attributes(self, token_id: int) -> list[Attribute]:

return attributes

async def gen_parse_attributes(self, token_id: int) -> list[Attribute]:
attributes = []

punk_attributes = (await self.gen_call(token_id, "punkAttributes(uint16)")).split(", ") # type: ignore[union-attr] # noqa: E501

type_attribute = Attribute(
trait_type="Type",
value=punk_attributes[0],
display_type=None,
)
attributes.append(type_attribute)

for value in punk_attributes[1:]:
attribute = Attribute(
trait_type="Accessory",
value=value,
display_type=None,
)
attributes.append(attribute)

return attributes

def parse_metadata(self, token: Token, *args, **kwargs) -> Metadata: # type: ignore[no-untyped-def] # noqa: E501
token.uri = f"https://api.wrappedpunks.com/api/punks/metadata/{token.token_id}"
raw_data = self.fetcher.fetch_content(token.uri)
Expand All @@ -105,3 +148,24 @@ def parse_metadata(self, token: Token, *args, **kwargs) -> Metadata: # type: ig
image=image,
additional_fields=self.parse_additional_fields(raw_data), # type: ignore[arg-type] # noqa: E501
)

async def _gen_parse_metadata_impl(self, token: Token, *args, **kwargs) -> Metadata: # type: ignore[no-untyped-def] # noqa: E501
token.uri = f"https://api.wrappedpunks.com/api/punks/metadata/{token.token_id}"
raw_data, mime_and_size, image, attributes = await asyncio.gather(
self.fetcher.gen_fetch_content(token.uri),
self.fetcher.gen_fetch_mime_type_and_size(token.uri),
self.gen_image(token.token_id),
self.gen_parse_attributes(token.token_id),
)
mime, _ = mime_and_size

return Metadata(
token=token,
raw_data=raw_data,
attributes=attributes,
name=raw_data.get("name"), # type: ignore[union-attr]
description=raw_data.get("description"), # type: ignore[union-attr]
mime_type=mime,
image=image,
additional_fields=self.parse_additional_fields(raw_data), # type: ignore[arg-type] # noqa: E501
)
62 changes: 61 additions & 1 deletion offchain/metadata/parsers/collection/superrare.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import asyncio
from typing import Optional

from offchain.constants.addresses import CollectionAddress
Expand Down Expand Up @@ -29,6 +30,21 @@ def get_image_details(self, raw_data: dict) -> Optional[MediaDetails]: # type:
pass
return details

async def gen_image_details(self, raw_data: dict) -> Optional[MediaDetails]: # type: ignore[type-arg] # noqa: E501
image_uri = raw_data.get("image")
if not image_uri:
return None
details = MediaDetails(uri=image_uri, size=None, sha256=None, mime_type=None)
try:
content_type, size = await self.fetcher.gen_fetch_mime_type_and_size(
image_uri
)
details.mime_type = content_type
details.size = size
except Exception:
pass
return details

def get_content_details(self, raw_data: dict) -> Optional[MediaDetails]: # type: ignore[type-arg] # noqa: E501
media = raw_data.get("media")
if not media or not isinstance(media, dict):
Expand All @@ -52,6 +68,31 @@ def get_content_details(self, raw_data: dict) -> Optional[MediaDetails]: # type

return details

async def gen_content_details(self, raw_data: dict) -> Optional[MediaDetails]: # type: ignore[type-arg] # noqa: E501
media = raw_data.get("media")
if not media or not isinstance(media, dict):
return # type: ignore[return-value]
content_uri = media.get("uri")
if not content_uri:
return # type: ignore[return-value]
details = MediaDetails(
uri=content_uri,
size=media.get("size"),
sha256=None,
mime_type=media.get("mimeType"),
)
if not details.mime_type:
try:
content_type, size = await self.fetcher.gen_fetch_mime_type_and_size(
content_uri
)
details.mime_type = content_type
details.size = size
except Exception:
pass

return details

def parse_additional_fields(self, raw_data: dict) -> list[MetadataField]: # type: ignore[type-arg] # noqa: E501
additional_fields = []
if created_by := raw_data.get("createdBy"):
Expand Down Expand Up @@ -84,7 +125,6 @@ def parse_additional_fields(self, raw_data: dict) -> list[MetadataField]: # typ
return additional_fields

def parse_metadata(self, token: Token, raw_data: Optional[dict], *args, **kwargs) -> Optional[Metadata]: # type: ignore[no-untyped-def, type-arg] # noqa: E501

mime_type, _ = self.fetcher.fetch_mime_type_and_size(token.uri) # type: ignore[arg-type] # noqa: E501

return Metadata(
Expand All @@ -98,3 +138,23 @@ def parse_metadata(self, token: Token, raw_data: Optional[dict], *args, **kwargs
additional_fields=self.parse_additional_fields(raw_data), # type: ignore[arg-type] # noqa: E501
attributes=[],
)

async def _gen_parse_metadata_impl(self, token: Token, raw_data: Optional[dict], *args, **kwargs) -> Optional[Metadata]: # type: ignore[no-untyped-def, type-arg] # noqa: E501
mime_and_size, image, content = await asyncio.gather(
self.fetcher.gen_fetch_mime_type_and_size(token.uri), # type: ignore[arg-type] # noqa: E501
self.gen_image_details(raw_data), # type: ignore[arg-type] # noqa: E501
self.gen_content_details(raw_data), # type: ignore[arg-type] # noqa: E501
)
mime_type, _ = mime_and_size

return Metadata(
token=token,
raw_data=raw_data,
name=raw_data.get("name"), # type: ignore[union-attr]
description=raw_data.get("description"), # type: ignore[union-attr]
mime_type=mime_type,
image=image, # type: ignore[arg-type]
content=content, # type: ignore[arg-type]
additional_fields=self.parse_additional_fields(raw_data), # type: ignore[arg-type] # noqa: E501
attributes=[],
)
64 changes: 63 additions & 1 deletion offchain/metadata/parsers/collection/zora.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import asyncio
from typing import Optional

from offchain.constants.addresses import CollectionAddress
from offchain.metadata.models.metadata import (
Metadata,
MediaDetails,
Metadata,
MetadataField,
MetadataFieldType,
)
Expand Down Expand Up @@ -46,6 +47,19 @@ def get_uri(self, token_id: int) -> Optional[str]:

return results[0]

async def gen_uri(self, token_id: int) -> Optional[str]:
results = await self.contract_caller.rpc.async_reader.gen_call_single_function_single_address_many_args(
ADDRESS,
function_sig="tokenMetadataURI(uint256)",
return_type=["string"],
args=[[token_id]],
)

if len(results) < 1:
return None

return results[0]

def get_content_uri(self, token_id: int) -> Optional[str]:
results = self.contract_caller.single_address_single_fn_many_args(
ADDRESS,
Expand All @@ -59,6 +73,19 @@ def get_content_uri(self, token_id: int) -> Optional[str]:

return results[0]

async def gen_content_uri(self, token_id: int) -> Optional[str]:
results = await self.contract_caller.rpc.async_reader.gen_call_single_function_single_address_many_args(
ADDRESS,
function_sig="tokenURI(uint256)",
return_type=["string"],
args=[[token_id]],
)

if len(results) < 1:
return None

return results[0]

def get_content_details(self, uri: str) -> Optional[MediaDetails]:
try:
content_type, size = self.fetcher.fetch_mime_type_and_size(uri)
Expand All @@ -68,6 +95,15 @@ def get_content_details(self, uri: str) -> Optional[MediaDetails]:

return None

async def gen_content_details(self, uri: str) -> Optional[MediaDetails]:
try:
content_type, size = await self.fetcher.gen_fetch_mime_type_and_size(uri)
return MediaDetails(uri=uri, size=size, sha256=None, mime_type=content_type)
except Exception:
pass

return None

def parse_metadata(self, token: Token, raw_data: Optional[dict], *args, **kwargs) -> Optional[Metadata]: # type: ignore[no-untyped-def, type-arg] # noqa: E501
if token.uri is None or raw_data is None or not isinstance(raw_data, dict):
token.uri = self.get_uri(token.token_id)
Expand All @@ -89,3 +125,29 @@ def parse_metadata(self, token: Token, raw_data: Optional[dict], *args, **kwargs
metadata.mime_type = "application/json" # type: ignore[union-attr]

return metadata

async def _gen_parse_metadata_impl(self, token: Token, raw_data: Optional[dict], *args, **kwargs) -> Optional[Metadata]: # type: ignore[no-untyped-def, type-arg] # noqa: E501
if token.uri is None:
token.uri = await self.gen_uri(token.token_id)
if raw_data is None or not isinstance(raw_data, dict):
raw_data = await self.fetcher.gen_fetch_content( # type:ignore[assignment]
token.uri # type:ignore[arg-type]
)

metadata, content_uri = await asyncio.gather(
DefaultCatchallParser(self.fetcher).gen_parse_metadata(token=token, raw_data=raw_data), # type: ignore[arg-type] # noqa: E501
self.gen_content_uri(token.token_id),
)
content = await self.gen_content_details(content_uri) # type: ignore[arg-type]

# if we have an image, make sure we set
# the image field, otherwise fallback to content
if content.mime_type.startswith("image"): # type: ignore[union-attr]
metadata.image = content # type: ignore[union-attr]
else:
metadata.content = content # type: ignore[union-attr]

metadata.additional_fields = self.parse_additional_fields(raw_data) # type: ignore[arg-type, union-attr] # noqa: E501
metadata.mime_type = "application/json" # type: ignore[union-attr]

return metadata
55 changes: 55 additions & 0 deletions offchain/metadata/parsers/schema/opensea.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,61 @@ def parse_metadata(self, token: Token, raw_data: dict, *args, **kwargs) -> Optio
additional_fields=self.parse_additional_fields(raw_data),
)

async def _gen_parse_metadata_impl(self, token: Token, raw_data: dict, *args, **kwargs) -> Optional[Metadata]: # type: ignore[no-untyped-def, type-arg] # noqa: E501
"""Given a token and raw data returned from the token uri, return a normalized Metadata object.
Args:
token (Token): token to process metadata for.
raw_data (dict): raw data returned from token uri.
Returns:
Optional[Metadata]: normalized metadata object, if successfully parsed.
""" # noqa: E501
mime, _ = await self.fetcher.gen_fetch_mime_type_and_size(token.uri) # type: ignore[arg-type] # noqa: E501

attributes = [
self.parse_attribute(attribute)
for attribute in raw_data.get("attributes", [])
] # noqa: E501
image = None
image_uri = raw_data.get("image") or raw_data.get("image_data")
if image_uri:
image_mime, image_size = await self.fetcher.gen_fetch_mime_type_and_size(
image_uri
)
image = MediaDetails(size=image_size, uri=image_uri, mime_type=image_mime)

content = None
content_uri = raw_data.get("animation_url")
if content_uri:
(
content_mime,
content_size,
) = await self.fetcher.gen_fetch_mime_type_and_size(
content_uri
) # noqa: E501
content = MediaDetails(
uri=content_uri, size=content_size, mime_type=content_mime
) # noqa: E501

if image and image.mime_type:
mime = image.mime_type

if content and content.mime_type:
mime = content.mime_type

return Metadata(
token=token,
raw_data=raw_data,
attributes=attributes,
name=raw_data.get("name"),
description=raw_data.get("description"),
mime_type=mime,
image=image,
content=content,
additional_fields=self.parse_additional_fields(raw_data),
)

def should_parse_token(self, token: Token, raw_data: Optional[dict], *args, **kwargs) -> bool: # type: ignore[no-untyped-def, type-arg] # noqa: E501
"""Return whether or not a collection parser should parse a given token.
Expand Down
Loading

0 comments on commit 7a16820

Please sign in to comment.