Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 44 additions & 42 deletions ipfshttpclient/client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from . import bootstrap
from . import config
#TODO: `from . import dag`
from . import dag
Comment on lines 25 to +26
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You forgot to remove the TODO comment above 😉

from . import dht
from . import files
from . import key
Expand All @@ -40,8 +41,8 @@


def assert_version(version: str, minimum: str = VERSION_MINIMUM,
maximum: str = VERSION_MAXIMUM,
blacklist: ty.Iterable[str] = VERSION_BLACKLIST) -> None:
maximum: str = VERSION_MAXIMUM,
blacklist: ty.Iterable[str] = VERSION_BLACKLIST) -> None:
Comment on lines +44 to +45
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file got littered with some problematic whitespace changes unfortunately. I'll update your commit to remove this part of the diff, please don't be offended.

Which program did you use to edit the file?

"""Make sure that the given daemon version is supported by this client
version.

Expand All @@ -67,7 +68,7 @@ def assert_version(version: str, minimum: str = VERSION_MINIMUM,

if minimum > version or version >= maximum:
raise exceptions.VersionMismatch(version, minimum, maximum)

for blacklisted in blacklist:
blacklisted = list(map(int, blacklisted.split('-', 1)[0].split('.')))
if version == blacklisted:
Expand All @@ -77,24 +78,24 @@ def assert_version(version: str, minimum: str = VERSION_MINIMUM,
def connect(
addr: http.addr_t = DEFAULT_ADDR,
base: str = DEFAULT_BASE, *,

chunk_size: int = multipart.default_chunk_size,
offline: bool = False,
session: bool = False,

auth: http.auth_t = None,
cookies: http.cookies_t = None,
headers: http.headers_t = {},
timeout: http.timeout_t = 120,

# Backward-compat
username: ty.Optional[str] = None,
password: ty.Optional[str] = None
):
"""Create a new :class:`~ipfshttpclient.Client` instance and connect to the
daemon to validate that its version is supported as well as applying any
known workarounds for the given daemon version

Raises
------
~ipfshttpclient.exceptions.VersionMismatch
Expand All @@ -103,7 +104,7 @@ def connect(
~ipfshttpclient.exceptions.ProtocolError
~ipfshttpclient.exceptions.StatusError
~ipfshttpclient.exceptions.TimeoutError

All parameters are identical to those passed to the constructor of the
:class:`~ipfshttpclient.Client` class.
"""
Expand All @@ -114,39 +115,39 @@ def connect(
auth=auth, cookies=cookies, headers=headers, timeout=timeout,
username=username, password=password,
)

# Query version number from daemon and validate it
assert_version(client.apply_workarounds()["Version"])

return client


class Client(files.Base, miscellaneous.Base):
"""The main IPFS HTTP client class

Allows access to an IPFS daemon instance using its HTTP API by exposing an
`IPFS Interface Core <https://github.com/ipfs/interface-ipfs-core/tree/master/SPEC>`__
compatible set of methods.

It is possible to instantiate this class directly, using the same parameters
as :func:`connect`, to prevent the client from checking for an active and
compatible version of the daemon. In general however, calling
:func:`connect` should be preferred.

In order to reduce latency between individual API calls, this class may keep
a pool of TCP connections between this client and the API daemon open
between requests. The only caveat of this is that the client object should
be closed when it is not used anymore to prevent resource leaks.

The easiest way of using this “session management” facility is using a
context manager::

with ipfshttpclient.connect() as client:
print(client.version()) # These calls…
print(client.version()) # …will reuse their TCP connection

A client object may be re-opened several times::

client = ipfshttpclient.connect()
print(client.version()) # Perform API call on separate TCP connection
with client:
Expand All @@ -155,26 +156,27 @@ class Client(files.Base, miscellaneous.Base):
with client:
print(client.version()) # These calls…
print(client.version()) # …will share a different TCP connection

When storing a long-running :class:`Client` object use it like this::

class Consumer:
def __init__(self):
self._client = ipfshttpclient.connect(session=True)

# … other code …

def close(self): # Call this when you're done
self._client.close()
"""

# Fix up docstring so that Sphinx doesn't ignore the constructors parameter list
__doc__ += "\n\n" + "\n".join(l[1:] for l in base.ClientBase.__init__.__doc__.split("\n"))

bitswap = base.SectionProperty(bitswap.Section)
block = base.SectionProperty(block.Section)
bootstrap = base.SectionProperty(bootstrap.Section)
config = base.SectionProperty(config.Section)
dag = base.SectionProperty(dag.Section)
dht = base.SectionProperty(dht.Section)
key = base.SectionProperty(key.Section)
name = base.SectionProperty(name.Section)
Expand All @@ -184,23 +186,23 @@ def close(self): # Call this when you're done
repo = base.SectionProperty(repo.Section)
swarm = base.SectionProperty(swarm.Section)
unstable = base.SectionProperty(unstable.Section)


######################
# SESSION MANAGEMENT #
######################

def __enter__(self):
self._client.open_session()
return self

def __exit__(self, exc_type, exc_value, traceback):
self.close()

def close(self):
"""Close any currently open client session and free any associated
resources.

If there was no session currently open this method does nothing. An open
session is not a requirement for using a :class:`~ipfshttpclient.Client`
object and as such all method defined on it will continue to work, but
Expand All @@ -209,34 +211,34 @@ def close(self):
in the future. See the class's description for details.
"""
self._client.close_session()


###########
# HELPERS #
###########

def apply_workarounds(self):
"""Query version information of the referenced daemon and enable any
workarounds known for the corresponding version

Returns
-------
dict
The version information returned by the daemon
"""
version_info = self.version()

version = tuple(map(int, version_info["Version"].split('-', 1)[0].split('.')))

self._workarounds.clear()
if version < (0, 5): # pragma: no cover (workaround)
# Not really a workaround, but make use of HEAD requests on versions
# that support them to speed things up if we are not interested in the
# response anyways
self._workarounds.add("use_http_head_for_no_result")

return version_info

@utils.return_field('Hash')
@base.returns_single_item(dict)
def add_bytes(self, data: bytes, **kwargs):
Expand All @@ -261,7 +263,7 @@ def add_bytes(self, data: bytes, **kwargs):
"""
body, headers = multipart.stream_bytes(data, chunk_size=self.chunk_size)
return self._client.request('/add', decoder='json',
data=body, headers=headers, **kwargs)
data=body, headers=headers, **kwargs)

@utils.return_field('Hash')
@base.returns_single_item(dict)
Expand All @@ -287,7 +289,7 @@ def add_str(self, string, **kwargs):
"""
body, headers = multipart.stream_text(string, chunk_size=self.chunk_size)
return self._client.request('/add', decoder='json',
data=body, headers=headers, **kwargs)
data=body, headers=headers, **kwargs)

def add_json(self, json_obj, **kwargs):
"""Adds a json-serializable Python dict as a json file to IPFS.
Expand All @@ -308,8 +310,8 @@ def add_json(self, json_obj, **kwargs):
Hash of the added IPFS object
"""
return self.add_bytes(encoding.Json().encode(json_obj), **kwargs)


@base.returns_single_item()
def get_json(self, cid, **kwargs):
"""Loads a json object from IPFS.
Expand All @@ -329,4 +331,4 @@ def get_json(self, cid, **kwargs):
object
Deserialized IPFS JSON object value
"""
return self.cat(cid, decoder='json', **kwargs)
return self.cat(cid, decoder='json', **kwargs)
Loading