Source code for ipfshttpclient.client

"""IPFS API Bindings for Python.

Classes:

 * Client – a TCP client for interacting with an IPFS daemon
"""

import os
import typing as ty
import warnings

import multiaddr

DEFAULT_ADDR = multiaddr.Multiaddr(os.environ.get("PY_IPFS_HTTP_CLIENT_DEFAULT_ADDR", '/dns/localhost/tcp/5001/http'))
DEFAULT_BASE = str(os.environ.get("PY_IPFS_HTTP_CLIENT_DEFAULT_BASE", 'api/v0'))

VERSION_MINIMUM   = "0.4.22"
VERSION_BLACKLIST = []
VERSION_MAXIMUM   = "0.7.0"

from . import bitswap
from . import block
from . import bootstrap
from . import config
from . import dag
from . import dht
from . import files
from . import key
from . import miscellaneous
from . import name
from . import object
from . import pin
from . import pubsub
from . import repo
#TODO: `from . import stats`
from . import swarm
from . import unstable

from .. import encoding, exceptions, http, multipart, utils


[docs]def assert_version(version: str, minimum: str = VERSION_MINIMUM, maximum: str = VERSION_MAXIMUM, blacklist: ty.Iterable[str] = VERSION_BLACKLIST) -> None: """Make sure that the given daemon version is supported by this client version. Raises ------ ~ipfshttpclient.exceptions.VersionMismatch Parameters ---------- version The actual version of an IPFS daemon minimum The minimal IPFS daemon version allowed (inclusive) maximum The maximum IPFS daemon version allowed (exclusive) blacklist Versions explicitly disallowed even if in range *minimum* – *maximum* """ # Convert version strings to integer tuples version = list(map(int, version.split('-', 1)[0].split('.'))) minimum = list(map(int, minimum.split('-', 1)[0].split('.'))) maximum = list(map(int, maximum.split('-', 1)[0].split('.'))) 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: raise exceptions.VersionMismatch(version, minimum, maximum)
[docs]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 ~ipfshttpclient.exceptions.ErrorResponse ~ipfshttpclient.exceptions.ConnectionError ~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. """ # Create client instance client = Client( addr, base, chunk_size=chunk_size, offline=offline, session=session, 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
[docs]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: print(client.version()) # These calls… print(client.version()) # …will share a TCP connection 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) object = base.SectionProperty(object.Section) pin = base.SectionProperty(pin.Section) pubsub = base.SectionProperty(pubsub.Section) 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()
[docs] 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 a new TCP connection will be established for each and every API call invoked. Such a usage should therefor be avoided and may cause a warning in the future. See the class's description for details. """ self._client.close_session()
########### # HELPERS # ###########
[docs] 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
[docs] @utils.return_field('Hash') @base.returns_single_item(dict) def add_bytes(self, data: bytes, **kwargs): """Adds a set of bytes as a file to IPFS. .. code-block:: python >>> client.add_bytes(b"Mary had a little lamb") 'QmZfF6C9j4VtoCsTp4KSrhYH47QMd3DNXVZBKaxJdhaPab' Also accepts and will stream generator objects. Parameters ---------- data Content to be added as a file Returns ------- str Hash of the added IPFS object """ body, headers = multipart.stream_bytes(data, chunk_size=self.chunk_size) return self._client.request('/add', decoder='json', data=body, headers=headers, **kwargs)
[docs] @utils.return_field('Hash') @base.returns_single_item(dict) def add_str(self, string, **kwargs): """Adds a Python string as a file to IPFS. .. code-block:: python >>> client.add_str(u"Mary had a little lamb") 'QmZfF6C9j4VtoCsTp4KSrhYH47QMd3DNXVZBKaxJdhaPab' Also accepts and will stream generator objects. Parameters ---------- string : str Content to be added as a file Returns ------- str Hash of the added IPFS object """ body, headers = multipart.stream_text(string, chunk_size=self.chunk_size) return self._client.request('/add', decoder='json', data=body, headers=headers, **kwargs)
[docs] def add_json(self, json_obj, **kwargs): """Adds a json-serializable Python dict as a json file to IPFS. .. code-block:: python >>> client.add_json({'one': 1, 'two': 2, 'three': 3}) 'QmVz9g7m5u3oHiNKHj2CJX1dbG1gtismRS3g9NaPBBLbob' Parameters ---------- json_obj : dict A json-serializable Python dictionary Returns ------- str Hash of the added IPFS object """ return self.add_bytes(encoding.Json().encode(json_obj), **kwargs)
[docs] @base.returns_single_item() def get_json(self, cid, **kwargs): """Loads a json object from IPFS. .. code-block:: python >>> client.get_json('QmVz9g7m5u3oHiNKHj2CJX1dbG1gtismRS3g9NaPBBLbob') {'one': 1, 'two': 2, 'three': 3} Parameters ---------- cid : Union[str, cid.CIDv0, cid.CIDv1] CID of the IPFS object to load Returns ------- object Deserialized IPFS JSON object value """ return self.cat(cid, decoder='json', **kwargs)