Skip to content

Commit 564b6c4

Browse files
authored
Merge pull request #100 from ourzora/BACK-2650
BACK-2650: fix DataURIAdapter re plain text json data uri
2 parents d25fb44 + ff6beb5 commit 564b6c4

File tree

6 files changed

+56
-10
lines changed

6 files changed

+56
-10
lines changed

docs/changelog.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## v0.3.2
4+
5+
- Fix an issue in `DataURIAdapter` where plain-text json data uri would get ignored
6+
37
## v0.3.1
48

59
- Trim token_uri in some log outputs, this is mainly useful for data uris that are too long and make logs unreadable

docs/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Getting Started
22

3-
Documentation for version: **v0.3.1**
3+
Documentation for version: **v0.3.2**
44

55
## Overview
66

offchain/metadata/adapters/data_uri.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from urllib.request import urlopen
44

55
import httpx
6+
import json
67
from requests import PreparedRequest, Response
78

89
from offchain.metadata.adapters.base_adapter import BaseAdapter
@@ -17,6 +18,10 @@ def decode_data_url(data_url): # type: ignore[no-untyped-def]
1718
decoded_data = base64.b64decode(data)
1819
decoded_text = decoded_data.decode("utf-8")
1920
return decoded_text
21+
elif "json;utf8" in data_parts[0]:
22+
decoded_data = urlopen(data_url).read()
23+
decoded_text = json.dumps(json.loads(decoded_data))
24+
return decoded_text
2025

2126
return None
2227

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "offchain"
3-
version = "0.3.1"
3+
version = "0.3.2"
44
description = "Open source metadata processing framework"
55
authors = ["Zora eng <[email protected]>"]
66
readme = "README.md"

tests/metadata/adapters/test_data_adapter.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import httpx
2+
import json
23
import pytest
34
from pytest_httpx import HTTPXMock
45

@@ -25,6 +26,25 @@ async def test_gen_head(self, httpx_mock: HTTPXMock):
2526
outgoing_request = httpx_mock.get_requests()
2627
assert not outgoing_request
2728

29+
@pytest.mark.asyncio
30+
async def test_gen_head_not_base64(self, httpx_mock: HTTPXMock):
31+
adapter = DataURIAdapter()
32+
data_url = "data:application/json;utf8,{\"name\":\"here for now\",\"description\":\"sometimes i don't know how to feel when i'm away.\", \"image\": \"data:image/svg+xml;base64,PHN2ZyBpZD0iaDNpNXR6IiB2aWV3Qm94PSIwIDAgNDggNDgiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHZlcnNpb249IjEuMSIgPgoKPGltYWdlIHg9IjAiIHk9IjAiIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgaW1hZ2UtcmVuZGVyaW5nPSJwaXhlbGF0ZWQiIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaWRZTWlkIiB4bGluazpocmVmPSJkYXRhOmltYWdlL3BuZztiYXNlNjQsaVZCT1J3MEtHZ29BQUFBTlNVaEVVZ0FBQUZBQUFBQlFDQU1BQUFDNXp3S2ZBQUFBWUZCTVZFVlFZWHIveUFFK1QxNy9rZ0I0YVdJUkVST3RwcHNhSGl3QUFBQVhtc3pKeGJ4MFhCUndxY2IvN2dKbGJIOWZiNGE1NWQ3L3RBRFBsdzFyWEZmOTVpMFBEQXVBa3F5bGhqTFQ4KzdLeU1jL1BUamh3ai8yeHk0VmFvcU1yc0t4MTlDSW1CMllBQUFENUVsRVFWUjRBZTJVaVpLak9CQkVzUllKdWZFMUFsbzJxSGYrL3k4M2xTQUt4Z1NodnZaKzl1QjB0WGxSTlFVVXh4dzBPZWJ3VndqZnRQYXRjNjJuOHROQ3JmczJ1RGJpUXFYMXA0VlYxWWVCd3VDSHZxbytLOVFoQk9kYmp1emNFSUwrckhCd2JoYUNyeEhDQ0w1VzZCeU1meFBoMjRwbjRmcnZHVUt0dXlYWXNnanhwVnVpZFphd1hLSjFQL2cyNG9laDBsMjVKRmRvU29NM1hnME9UU08zWHNNcUQvekZ4NFR4UkVLWENKdHM0V0NDTVVOOEJZUE1aUHhjR0ppWU00V3RPWnVoTlhpZERUTFRNSnhUb1dYQnhFS204R0VleHNURFN6dzhYaGI1bDBKMmg2M0JtNzFJUTVLSHVmRFJwVWpoZzF2ZW90bW81QXFiYkRLRm9OTnJ1bzBLeUJJU3JWNWVTdklTa2ZoSXNWTkhJVk40dTkvdm8xRGlnL0VEd2g1YnZOOXV0OUtBcmZpUkRwdXlhV0piaHBFalB3eHFqQjhRR2hLRkVwa1l1MG9wdFNWVUU4ZlQ4U1FaUWdGM1dZb1BrMkxYRjBYQnM4YVhPaDFWcEpoUXNhb3VCSm4vaDhTQXJZZ09TVExDemRObllheE9RdVNNTGZmcFBPbVF3bGVDRG9HNi9IN0JHNWxDN3BOQ2lZOFVPeVhuUlJSUGx3N1hRcFV4OGxxb2tFUjRuWVZFcVNJS2Nha0FXaHJDeUNLRWFlVGtROXdhbWVDdjNMS1p0N3dSSUdRanlRZHcrdWJJcXNnU3lzajAwWTdUeDdRV29tdU0vSXhlOElzUUNXOEl0MGFtVUsyYll0VGxqT2JJWUMza3lQWUpyZTBtdXJ6ZDd2RXl2RVdoQnJOd1JJRTlZZmNrL0hrREN5R2FJbHBYVlRVSlgrMTV4dHJ4T0grZUZ5UWhMMnh0dXFydm81Q2QxYlgzM3RweEtmWXdBd09QbHJYeFk5M2g2R09IVVFnWHNiWHRPdS8xaFVJNVAwTkkzOCtGOEFRZ3JQRjNYMWZUbHUwUE1BcW53QUkvVmtMNjBLV01EQ0ZNdnF2eHo5WTloSkYwdnFLUWlQQjVLVEx5cTZvNnVHcThmVHpPUWs1STRVSGd5SW5uTFhOa0gyMzFBVG9MMUNXTi9Cc1loUWdhVEFVZUU3TGxKTFNqejBaaHo1dDVzOE9kcFNRbW9jZWM0T0FHNSsxOFlZTjFoeFpJaDV1WERaZGlhL2JYTzNEQXpKT1FJK2NMNmJ1elE0ditJaitDRFhYd2ZubzQ3SGU0WUxwc1pDbnNUOGVKd3dGeEpSenBsTjFsTGJSUmlONEc2MXpnYmdyWjhqc3BTN3ZBKytDQ0NLWERmTFJlQ2V2Z1hBMm1od01XdmZ1UTRRMXVsMVg1RXJIOVdRY0lBWVNLdTk2OS9KTHdJSnlYMzZ3N09EY0tMVnp3dldVTDdlSTNFZ2JzcEZvSXdlNURCc0tOb2xMekl3cjlVamNKSXhTU2ZTRklGbmxFMWRRUlcwenNQMlE0OHJwS0lkTzJjUGNoQStIcTYvZ2J1YjNxbWI1SVpDeEZPSUJWaHdrdnd0MW5Bb1NwdU5laGw0RnpoWUpkQ24yMDRTQSt0U0VVWnFGVXJRaVpjRWltQ2Z0dVlKQzBnZnhxZzJMMUlSYUpHK1FJcDArUmZLOVFGZC9GRlUvWjYrWDZQaTQ0YTVtRmExRjlNZjhML3hmK0hZUi9BRVJPMzlYOE5Fb1VBQUFBQUVsRlRrU3VRbUNDIi8+Cgo8L3N2Zz4=\"}" # noqa
33+
async with httpx.AsyncClient() as client:
34+
result = await adapter.gen_head(url=data_url, sess=client)
35+
36+
expected = httpx.Response(
37+
status_code=200,
38+
headers={"content-type": "application/json;utf8", "content-length": "2600"},
39+
request=httpx.Request(method="HEAD", url=data_url),
40+
)
41+
assert result.status_code == 200
42+
assert result.request.method == "HEAD"
43+
assert result.headers == expected.headers
44+
# no real request was made
45+
outgoing_request = httpx_mock.get_requests()
46+
assert not outgoing_request
47+
2848
@pytest.mark.asyncio
2949
async def test_gen_send(self, httpx_mock: HTTPXMock):
3050
adapter = DataURIAdapter()
@@ -37,3 +57,18 @@ async def test_gen_send(self, httpx_mock: HTTPXMock):
3757
# no real request was made
3858
outgoing_request = httpx_mock.get_requests()
3959
assert not outgoing_request
60+
61+
@pytest.mark.asyncio
62+
async def test_gen_send_not_base64(self, httpx_mock: HTTPXMock):
63+
adapter = DataURIAdapter()
64+
json_str = "{\"name\":\"here for now\",\"description\":\"sometimes i don't know how to feel when i'm away.\", \"image\": \"data:image/svg+xml;base64,PHN2ZyBpZD0iaDNpNXR6IiB2aWV3Qm94PSIwIDAgNDggNDgiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHZlcnNpb249IjEuMSIgPgoKPGltYWdlIHg9IjAiIHk9IjAiIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgaW1hZ2UtcmVuZGVyaW5nPSJwaXhlbGF0ZWQiIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaWRZTWlkIiB4bGluazpocmVmPSJkYXRhOmltYWdlL3BuZztiYXNlNjQsaVZCT1J3MEtHZ29BQUFBTlNVaEVVZ0FBQUZBQUFBQlFDQU1BQUFDNXp3S2ZBQUFBWUZCTVZFVlFZWHIveUFFK1QxNy9rZ0I0YVdJUkVST3RwcHNhSGl3QUFBQVhtc3pKeGJ4MFhCUndxY2IvN2dKbGJIOWZiNGE1NWQ3L3RBRFBsdzFyWEZmOTVpMFBEQXVBa3F5bGhqTFQ4KzdLeU1jL1BUamh3ai8yeHk0VmFvcU1yc0t4MTlDSW1CMllBQUFENUVsRVFWUjRBZTJVaVpLak9CQkVzUllKdWZFMUFsbzJxSGYrL3k4M2xTQUt4Z1NodnZaKzl1QjB0WGxSTlFVVXh4dzBPZWJ3VndqZnRQYXRjNjJuOHROQ3JmczJ1RGJpUXFYMXA0VlYxWWVCd3VDSHZxbytLOVFoQk9kYmp1emNFSUwrckhCd2JoYUNyeEhDQ0w1VzZCeU1meFBoMjRwbjRmcnZHVUt0dXlYWXNnanhwVnVpZFphd1hLSjFQL2cyNG9laDBsMjVKRmRvU29NM1hnME9UU08zWHNNcUQvekZ4NFR4UkVLWENKdHM0V0NDTVVOOEJZUE1aUHhjR0ppWU00V3RPWnVoTlhpZERUTFRNSnhUb1dYQnhFS204R0VleHNURFN6dzhYaGI1bDBKMmg2M0JtNzFJUTVLSHVmRFJwVWpoZzF2ZW90bW81QXFiYkRLRm9OTnJ1bzBLeUJJU3JWNWVTdklTa2ZoSXNWTkhJVk40dTkvdm8xRGlnL0VEd2g1YnZOOXV0OUtBcmZpUkRwdXlhV0piaHBFalB3eHFqQjhRR2hLRkVwa1l1MG9wdFNWVUU4ZlQ4U1FaUWdGM1dZb1BrMkxYRjBYQnM4YVhPaDFWcEpoUXNhb3VCSm4vaDhTQXJZZ09TVExDemRObllheE9RdVNNTGZmcFBPbVF3bGVDRG9HNi9IN0JHNWxDN3BOQ2lZOFVPeVhuUlJSUGx3N1hRcFV4OGxxb2tFUjRuWVZFcVNJS2Nha0FXaHJDeUNLRWFlVGtROXdhbWVDdjNMS1p0N3dSSUdRanlRZHcrdWJJcXNnU3lzajAwWTdUeDdRV29tdU0vSXhlOElzUUNXOEl0MGFtVUsyYll0VGxqT2JJWUMza3lQWUpyZTBtdXJ6ZDd2RXl2RVdoQnJOd1JJRTlZZmNrL0hrREN5R2FJbHBYVlRVSlgrMTV4dHJ4T0grZUZ5UWhMMnh0dXFydm81Q2QxYlgzM3RweEtmWXdBd09QbHJYeFk5M2g2R09IVVFnWHNiWHRPdS8xaFVJNVAwTkkzOCtGOEFRZ3JQRjNYMWZUbHUwUE1BcW53QUkvVmtMNjBLV01EQ0ZNdnF2eHo5WTloSkYwdnFLUWlQQjVLVEx5cTZvNnVHcThmVHpPUWs1STRVSGd5SW5uTFhOa0gyMzFBVG9MMUNXTi9Cc1loUWdhVEFVZUU3TGxKTFNqejBaaHo1dDVzOE9kcFNRbW9jZWM0T0FHNSsxOFlZTjFoeFpJaDV1WERaZGlhL2JYTzNEQXpKT1FJK2NMNmJ1elE0ditJaitDRFhYd2ZubzQ3SGU0WUxwc1pDbnNUOGVKd3dGeEpSenBsTjFsTGJSUmlONEc2MXpnYmdyWjhqc3BTN3ZBKytDQ0NLWERmTFJlQ2V2Z1hBMm1od01XdmZ1UTRRMXVsMVg1RXJIOVdRY0lBWVNLdTk2OS9KTHdJSnlYMzZ3N09EY0tMVnp3dldVTDdlSTNFZ2JzcEZvSXdlNURCc0tOb2xMekl3cjlVamNKSXhTU2ZTRklGbmxFMWRRUlcwenNQMlE0OHJwS0lkTzJjUGNoQStIcTYvZ2J1YjNxbWI1SVpDeEZPSUJWaHdrdnd0MW5Bb1NwdU5laGw0RnpoWUpkQ24yMDRTQSt0U0VVWnFGVXJRaVpjRWltQ2Z0dVlKQzBnZnhxZzJMMUlSYUpHK1FJcDArUmZLOVFGZC9GRlUvWjYrWDZQaTQ0YTVtRmExRjlNZjhML3hmK0hZUi9BRVJPMzlYOE5Fb1VBQUFBQUVsRlRrU3VRbUNDIi8+Cgo8L3N2Zz4=\"}" # noqa
65+
data_url = f"data:application/json;utf8,{json_str}"
66+
async with httpx.AsyncClient() as client:
67+
result = await adapter.gen_send(url=data_url, sess=client)
68+
69+
assert result.status_code == 200
70+
assert result.request.method == "GET"
71+
assert json.loads(result.text) == json.loads(json_str)
72+
# no real request was made
73+
outgoing_request = httpx_mock.get_requests()
74+
assert not outgoing_request

tests/metadata/parsers/test_zora_parser.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# flake8: noqa: E501
2-
from unittest.mock import MagicMock, Mock
2+
from unittest.mock import MagicMock, Mock, AsyncMock
33

44
import pytest
55

@@ -19,7 +19,7 @@ class TestZoraParser:
1919
token = Token(
2020
chain_identifier="ETHEREUM-MAINNET",
2121
collection_address="0xabefbc9fd2f806065b4f3c237d4b59d9a97bcac7",
22-
token_id=5769,
22+
token_id=31861,
2323
)
2424

2525
raw_data = {
@@ -46,8 +46,8 @@ def test_zora_parser_parses_metadata(self): # type: ignore[no-untyped-def]
4646
token=Token(
4747
chain_identifier="ETHEREUM-MAINNET",
4848
collection_address="0xabefbc9fd2f806065b4f3c237d4b59d9a97bcac7",
49-
token_id=5769,
50-
uri="https://zora-dev.mypinata.cloud/ipfs/bafkreigux6jujn5hvlmptgzgok4reaie2gkuvsk2kynnalsyfgr4g35dkm",
49+
token_id=31861,
50+
uri="https://gateway.pinata.cloud/ipfs/bafkreid3jq3mlqz4d3w7emkxpftmfjbbxtkwe7kf25lzp2krwcxfd57m6q",
5151
),
5252
raw_data={
5353
"description": "A Lonely Soul,\n\nI've felt lonely lately. Somewhere deep inside, detached. \n\nThere must be plenty of lost souls wandering the globe. Looking to belong; to understand their purpose.\n\nI know my purpose, but I fear I've burned up surviving to the moment.\n\nDoes this count?\nAm I still pushing forward?\n\nI hope so...\n\nDo I still have time?\nAm I just floating?\n\nPlease, don't give up.\n\nAge 23 (2021)\n4096x4096px",
@@ -63,7 +63,7 @@ def test_zora_parser_parses_metadata(self): # type: ignore[no-untyped-def]
6363
image=MediaDetails(
6464
size=13548199,
6565
sha256=None,
66-
uri="https://zora-dev.mypinata.cloud/ipfs/bafybeiffwxjez2axebcprj2h7wkohr2pdbvuv37f7uxyptuw7o6t5fvppu",
66+
uri="https://gateway.pinata.cloud/ipfs/bafybeifavbhn6ys3k4tvngt4rxkoo7vabiv4lnlszwkvdncjg245qz5chq",
6767
mime_type="image/jpeg",
6868
),
6969
content=None,
@@ -82,15 +82,17 @@ async def test_zora_parser_gen_parses_metadata(self): # type: ignore[no-untyped
8282
fetcher = MetadataFetcher()
8383
contract_caller = ContractCaller()
8484
parser = ZoraParser(fetcher=fetcher, contract_caller=contract_caller) # type: ignore[abstract]
85+
fetcher.gen_fetch_mime_type_and_size = AsyncMock(side_effect=[("application/json", 0), ("image/jpeg", 13548199)]) # type: ignore[assignment]
86+
fetcher.gen_fetch_content = AsyncMock(return_value=self.raw_data) # type: ignore[assignment]
8587
metadata = await parser.gen_parse_metadata(
8688
token=self.token, raw_data=self.raw_data
8789
)
8890
assert metadata == Metadata(
8991
token=Token(
9092
collection_address="0xabefbc9fd2f806065b4f3c237d4b59d9a97bcac7",
91-
token_id=5769,
93+
token_id=31861,
9294
chain_identifier="ETHEREUM-MAINNET",
93-
uri="https://zora-dev.mypinata.cloud/ipfs/bafkreigux6jujn5hvlmptgzgok4reaie2gkuvsk2kynnalsyfgr4g35dkm",
95+
uri="https://gateway.pinata.cloud/ipfs/bafkreid3jq3mlqz4d3w7emkxpftmfjbbxtkwe7kf25lzp2krwcxfd57m6q",
9496
),
9597
raw_data={
9698
"description": "A Lonely Soul,\n\nI've felt lonely lately. Somewhere deep inside, detached. \n\nThere must be plenty of lost souls wandering the globe. Looking to belong; to understand their purpose.\n\nI know my purpose, but I fear I've burned up surviving to the moment.\n\nDoes this count?\nAm I still pushing forward?\n\nI hope so...\n\nDo I still have time?\nAm I just floating?\n\nPlease, don't give up.\n\nAge 23 (2021)\n4096x4096px",
@@ -106,7 +108,7 @@ async def test_zora_parser_gen_parses_metadata(self): # type: ignore[no-untyped
106108
image=MediaDetails(
107109
size=13548199,
108110
sha256=None,
109-
uri="https://zora-dev.mypinata.cloud/ipfs/bafybeiffwxjez2axebcprj2h7wkohr2pdbvuv37f7uxyptuw7o6t5fvppu",
111+
uri="https://gateway.pinata.cloud/ipfs/bafybeifavbhn6ys3k4tvngt4rxkoo7vabiv4lnlszwkvdncjg245qz5chq",
110112
mime_type="image/jpeg",
111113
),
112114
content=None,

0 commit comments

Comments
 (0)