Skip to content

Commit 4ce1a14

Browse files
authored
Merge pull request #61 from ipld/feature/implement-missing-features
Add feature parity with go-cid implementation
2 parents a9bec64 + 9cc253d commit 4ce1a14

File tree

12 files changed

+1997
-31
lines changed

12 files changed

+1997
-31
lines changed

cid/__init__.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,20 @@
44
__email__ = "dhruv@dhruvb.com"
55
__version__ = "0.4.0"
66

7-
from .cid import CIDv0, CIDv1, from_bytes, from_string, is_cid, make_cid # noqa: F401
7+
from .cid import ( # noqa: F401
8+
CIDJSONEncoder,
9+
CIDv0,
10+
CIDv1,
11+
extract_encoding,
12+
from_bytes,
13+
from_bytes_strict,
14+
from_reader,
15+
from_string,
16+
is_cid,
17+
make_cid,
18+
must_parse,
19+
parse_ipfs_path,
20+
)
21+
from .builder import Builder, V0Builder, V1Builder # noqa: F401
22+
from .prefix import Prefix # noqa: F401
23+
from .set import CIDSet # noqa: F401

cid/builder.py

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
"""Builder pattern for CID construction."""
2+
3+
from abc import ABC, abstractmethod
4+
import hashlib
5+
from typing import TYPE_CHECKING
6+
7+
import multihash
8+
9+
if TYPE_CHECKING:
10+
from .cid import CIDv0, CIDv1
11+
12+
13+
class Builder(ABC):
14+
"""Builder interface for CID construction."""
15+
16+
@abstractmethod
17+
def sum(self, data: bytes) -> "CIDv0 | CIDv1":
18+
"""
19+
Hash data and create CID.
20+
21+
:param bytes data: Data to hash
22+
:return: CID object
23+
:rtype: :py:class:`cid.CIDv0` or :py:class:`cid.CIDv1`
24+
"""
25+
pass
26+
27+
@abstractmethod
28+
def get_codec(self) -> str:
29+
"""
30+
Get current codec.
31+
32+
:return: Codec name
33+
:rtype: str
34+
"""
35+
pass
36+
37+
@abstractmethod
38+
def with_codec(self, codec: str) -> "Builder":
39+
"""
40+
Return new builder with different codec.
41+
42+
:param str codec: New codec name
43+
:return: New builder instance
44+
:rtype: :py:class:`cid.builder.Builder`
45+
"""
46+
pass
47+
48+
49+
class V0Builder(Builder):
50+
"""Builder for CIDv0."""
51+
52+
def sum(self, data: bytes) -> "CIDv0":
53+
"""
54+
Create CIDv0 from data.
55+
56+
:param bytes data: Data to hash
57+
:return: CIDv0 object
58+
:rtype: :py:class:`cid.CIDv0`
59+
"""
60+
from .cid import CIDv0
61+
62+
digest = hashlib.sha256(data).digest()
63+
mhash = multihash.encode(digest, "sha2-256")
64+
return CIDv0(mhash)
65+
66+
def get_codec(self) -> str:
67+
"""
68+
Get current codec (always "dag-pb" for CIDv0).
69+
70+
:return: Codec name
71+
:rtype: str
72+
"""
73+
return "dag-pb"
74+
75+
def with_codec(self, codec: str) -> Builder:
76+
"""
77+
Return new builder with different codec.
78+
79+
Changing codec from CIDv0 requires switching to V1Builder.
80+
81+
:param str codec: New codec name
82+
:return: New builder instance (V1Builder if codec changed)
83+
:rtype: :py:class:`cid.builder.Builder`
84+
"""
85+
if codec == "dag-pb":
86+
return self
87+
# Changing codec requires V1
88+
return V1Builder(codec=codec, mh_type="sha2-256")
89+
90+
91+
class V1Builder(Builder):
92+
"""Builder for CIDv1."""
93+
94+
def __init__(self, codec: str, mh_type: str, mh_length: int = -1) -> None:
95+
"""
96+
Create V1Builder.
97+
98+
:param str codec: Codec name
99+
:param str mh_type: Multihash type
100+
:param int mh_length: Multihash length (-1 for default)
101+
"""
102+
self.codec = codec
103+
self.mh_type = mh_type
104+
self.mh_length = mh_length
105+
106+
def sum(self, data: bytes) -> "CIDv1":
107+
"""
108+
Create CIDv1 from data.
109+
110+
:param bytes data: Data to hash
111+
:return: CIDv1 object
112+
:rtype: :py:class:`cid.CIDv1`
113+
"""
114+
from .cid import CIDv1
115+
116+
if self.mh_type == "sha2-256":
117+
digest = hashlib.sha256(data).digest()
118+
elif self.mh_type == "sha2-512":
119+
digest = hashlib.sha512(data).digest()
120+
else:
121+
msg = f"Hash type {self.mh_type} not fully implemented"
122+
raise NotImplementedError(msg)
123+
124+
mh_length = None if self.mh_length == -1 else self.mh_length
125+
mhash = multihash.encode(digest, self.mh_type, mh_length)
126+
return CIDv1(self.codec, mhash)
127+
128+
def get_codec(self) -> str:
129+
"""
130+
Get current codec.
131+
132+
:return: Codec name
133+
:rtype: str
134+
"""
135+
return self.codec
136+
137+
def with_codec(self, codec: str) -> Builder:
138+
"""
139+
Return new builder with different codec.
140+
141+
:param str codec: New codec name
142+
:return: New builder instance
143+
:rtype: :py:class:`cid.builder.Builder`
144+
"""
145+
if codec == self.codec:
146+
return self
147+
return V1Builder(codec=codec, mh_type=self.mh_type, mh_length=self.mh_length)

0 commit comments

Comments
 (0)