Skip to content

Document, test, and improve functionality. #46

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Aug 2, 2016
Merged
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
2 changes: 2 additions & 0 deletions .coveragerc-py35
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[html]
directory = htmlcov-py35
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ htmlcov*
**/__pycache__/
.cache
docs/build/
.python-version
1 change: 1 addition & 0 deletions fake_dir/fsdfgh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dsadsad
1 change: 1 addition & 0 deletions fake_dir/popoiopiu
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
oooofiopfsdpio
1 change: 1 addition & 0 deletions fake_dir/test2/fssdf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dsdsdsadsdsad
1 change: 1 addition & 0 deletions fake_dir/test2/llllg
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dsdsadjs
1 change: 1 addition & 0 deletions fake_dir/test3/ppppoooooooooo
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dsasasd
1 change: 0 additions & 1 deletion ipfsApi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,4 @@

from .client import *


__version__ = '0.2.3'
658 changes: 544 additions & 114 deletions ipfsApi/client.py

Large diffs are not rendered by default.

141 changes: 129 additions & 12 deletions ipfsApi/commands.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
"""Defines the skeleton of different command structures.

Classes:
Command -- A simple command that can make requests to a path.
ArgCommand -- A Command subclass for commands with arguments.
FileCommand -- A Command subclass for file-manipulation commands.
DownloadCommand -- A Command subclass for file download commands.
"""

from __future__ import absolute_import

import os
Expand All @@ -10,35 +19,105 @@


class Command(object):
"""Defines a command.

Public methods:
__init__ -- creates a Command that will make requests to a given path
request -- make a request to this command's path

Instance variables:
path -- the url path that this Command will make requests to
"""

def __init__(self, path):
"""Creates a Command.

Keyword arguments:
path -- the url path that this Command makes requests to
"""
self.path = path

def request(self, client, *args, **kwargs):
"""Makes a request to the client with arguments.

Keyword arguments:
client -- the HTTP client to use for the request
args -- unused unnamed arguments
kwargs -- additional arguments to HTTP client's request
"""
return client.request(self.path, **kwargs)


class ArgCommand(Command):
"""Defines a command that takes arguments.

Subclass of Command.

Public methods:
__init__ -- extends Command constructor to also take a number of required
arguments
request -- makes a request to the ArgCommand's path with given arguments

Instance variables:
path -- the url path of that this command will send data to
argc -- the number of arguments required by this command
"""

def __init__(self, path, argc=None):
"""Creates an ArgCommand.

Keyword arguments:
path -- the url path to which the command with send data
argc -- the number of arguments required by this command
"""
Command.__init__(self, path)
self.argc = argc

def request(self, client, *args, **kwargs):
"""Makes a request to the client with arguments.

Can raise an InvalidArgument if the wrong number of arguments is
provided.

Keyword arguments:
client -- the HTTP client to use for the request
args -- the arguments to the HTTP client's request
kwargs -- additional arguments to HTTP client's request
"""
if self.argc and len(args) != self.argc:
raise InvalidArguments("[%s] command requires %d arguments." % (
self.path, self.argc))
return client.request(self.path, args=args, **kwargs)


class FileCommand(Command):
"""Defines a command for manipulating files.

Subclass of Command.

Public methods:
request -- overrides Command's request to access a file or files
files -- adds file-like objects as a multipart request to IPFS
directory -- loads a directory recursively into IPFS

Instance variables:
path -- the path to make the file requests to
"""

def request(self, client, args, f, **kwargs):
"""
Takes either a file object, a filename, an iterable of filenames, an
iterable of file objects, or a heterogeneous iterable of file objects
and filenames. Can only take one directory at a time, which will be
"""Makes a request for a file or files.

Can only take one directory at a time, which will be
traversed (optionally recursive).

Keyword arguments:
client -- the http client to send requests to
args -- the arguments to the HTTP client's request
f -- a file object, a filename, an iterable of filenames, an
iterable of file objects, or a heterogeneous iterable of file
objects and filenames
kwargs -- additional arguments (include 'recursive' if recursively
copying a directory)
"""
if kwargs.pop('recursive', False):
return self.directory(client, args, f, recursive=True, **kwargs)
Expand All @@ -47,29 +126,67 @@ def request(self, client, args, f, **kwargs):
else:
return self.files(client, args, f, **kwargs)

def files(self, client, args, files, chunk_size=default_chunk_size, **kwargs):
"""
Adds file-like objects as a multipart request to IPFS.
def files(self, client, args, files,
chunk_size=default_chunk_size, **kwargs):
"""Adds file-like objects as a multipart request to IPFS.

Keyword arguments:
client -- the http client to send requests to
args -- the arguments to the HTTP client's request
files -- the files being requested
chunk_size -- the size of the chunks to break the files into
kwargs -- additional arguments to HTTP client's request
"""
body, headers = multipart.stream_files(files,
chunk_size=chunk_size)
return client.request(self.path, args=args, data=body, headers=headers, **kwargs)
return client.request(self.path, args=args, data=body,
headers=headers, **kwargs)

def directory(self, client, args, dirname,
match='*', recursive=False,
chunk_size=default_chunk_size, **kwargs):
"""
Loads a directory recursively into IPFS, files are matched against the
given pattern.
"""Loads a directory recursively into IPFS.

Files are matched against the given pattern.

Keyword arguments:
client -- the http client to send requests to
args -- the arguments to the HTTP client's request
dirname -- the name of the directory being requested
match -- a pattern to match the files against
recursive -- boolean for whether to load contents recursively
chunk_size -- the size of the chunks to break the files into
kwargs -- additional arguments to HTTP client's request
"""
body, headers = multipart.stream_directory(dirname,
fnpattern=match,
recursive=recursive,
chunk_size=chunk_size)
return client.request(self.path, args=args, data=body, headers=headers, **kwargs)
return client.request(self.path, args=args, data=body,
headers=headers, **kwargs)


class DownloadCommand(Command):
"""Downloads requested files.

Subclass of Command

Public methods:
request -- make a request to this DownloadCommand's path to download a
given file

Instance variables:
path -- the url path to send requests to
"""

def request(self, client, *args, **kwargs):
"""Requests a download from the HTTP Client.

See the HTTP client's doc for details of what to pass in.

Keyword arguments:
client -- the http client to send requests to
args -- the arguments to the HTTP client
kwargs -- additional arguments to the HTTP client
"""
return client.download(self.path, args=args, **kwargs)
54 changes: 47 additions & 7 deletions ipfsApi/encoding.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
"""Defines encoding related classes.

Classes:
Encoding - An abstract based for a data parser/encoder interface.
Json - A subclass of Encoding that handles JSON parsing and encoding.
Protobuf - A subclass of Encoding to handle Protobuf parsing/encoding. TO DO
Xml - A subclass of Encoding to handle Xml parsing and encoding. TO DO

Functions:
get_encoding(name) - Retrieves the Encoder object for the named encoding.
"""

from __future__ import absolute_import

import json
Expand All @@ -6,34 +18,54 @@


class Encoding(object):
"""
Abstract base for a data parser/encoder interface interface
"""Abstract base for a data parser/encoder interface.

Public methods:
parse -- parses string into corresponding encoding
encode - serialize a raw object into corresponding encoding
"""

def parse(self, string):
"""Parses string into corresponding encoding.

Keyword arguments:
string - string to be parsed
"""
raise NotImplemented

def encode(self, obj):
"""Serialize a raw object into corresponding encoding.

Keyword arguments:
obj - object to be encoded.
"""
raise NotImplemented


class Json(Encoding):
"""
JSON parser/encoder that handles concatenated JSON
"""JSON parser/encoder that handles concatenated JSON.

Public methods:
__init__ -- creates a Json encoder/decoder
parse -- returns a Python object decoded from JSON object(s) in raw
encode -- returns obj serialized as JSON formatted string
"""
name = 'json'

def __init__(self):
"""Creates a JSON encoder/decoder"""
self.encoder = json.JSONEncoder()
self.decoder = json.JSONDecoder()

def parse(self, raw):
"""
Returns a Python object decoded from JSON object(s) in raw
"""Returns a Python object decoded from JSON object(s) in raw

Some responses from the IPFS api are a concatenated string of JSON
objects, which crashes json.loads(), so we need to use this instead as
a general approach.

Keyword arguments:
raw -- raw JSON object
"""
json_string = raw.strip()
results = []
Expand All @@ -54,16 +86,21 @@ def parse(self, raw):

def encode(self, obj):
"""
Returns obj encoded as JSON in a binary string
Returns obj serialized as JSON formatted string

Keyword arguments:
obj -- generic Python object
"""
return json.dumps(obj)


class Protobuf(Encoding):
"""Protobuf parser/encoder that handles protobuf."""
name = 'protobuf'


class Xml(Encoding):
"""XML parser/encoder that handles XML."""
name = 'xml'

# encodings supported by the IPFS api (default is json)
Expand All @@ -77,6 +114,9 @@ class Xml(Encoding):
def get_encoding(name):
"""
Returns an Encoder object for the named encoding

Keyword arguments:
name - named encoding. Supported options: Json, Protobuf, Xml
"""
try:
return __encodings[name.lower()]()
Expand Down
18 changes: 18 additions & 0 deletions ipfsApi/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,40 @@
"""Defines the skeleton for exceptions.

Classes:
ipfsApiError - A base class for generic exceptions.
InvalidCommand - An ipfsApiError subclass for invalid commands.
InvalidArguments - An ipfsApiError subclass for invalid arguments.
InvalidPath - An ipfsApiError subclass for invalid path.
FileCommandException - An ipfsApiError subclass for file command exceptions.
EncodingException - An ipfsApiError subclass for encoding exceptions.
"""


class ipfsApiError(Exception):
"""Base class for exceptions in this module."""
pass


class InvalidCommand(ipfsApiError):
"""Exception raised for an invalid command."""
pass


class InvalidArguments(ipfsApiError):
"""Exception raised for invalid arguments."""
pass


class InvalidPath(ipfsApiError):
"""Exception raised for an invalid path."""
pass


class FileCommandException(ipfsApiError):
"""Exception raised for file command exception."""
pass


class EncodingException(ipfsApiError):
"""Exception raised for invalid encoding."""
pass
Loading