Refactoring API, writing docs.

This commit is contained in:
Romain Dorgueil
2017-12-27 11:32:47 +01:00
parent 018289dad6
commit 46a8fd192e
12 changed files with 91 additions and 91 deletions

View File

@ -1,4 +1,4 @@
# Generated by Medikit 0.4.5 on 2017-12-13. # Generated by Medikit 0.4.5 on 2017-12-27.
# All changes will be overriden. # All changes will be overriden.
PACKAGE ?= bonobo PACKAGE ?= bonobo

View File

@ -7,7 +7,7 @@ python = require('python')
sphinx = require('sphinx') sphinx = require('sphinx')
yapf = require('yapf') yapf = require('yapf')
# python.set_versions('3.5', '3.6', '3.7') --> not yet implemented # python.set_versions('3.5', '3.6', '3.7') --> not yet implemented in medikit
python.setup( python.setup(
name='bonobo', name='bonobo',
@ -43,34 +43,30 @@ python.setup(
) )
python.add_requirements( python.add_requirements(
'fs >=2.0,<2.1', 'fs ~=2.0',
'graphviz >=0.8,<0.9', 'graphviz >=0.8,<0.9',
'jinja2 >=2.9,<3', 'jinja2 ~=2.9',
'mondrian >=0.6,<0.7', 'mondrian ~=0.6',
'packaging >=16,<17', 'packaging ~=16.0',
'psutil >=5.4,<6', 'psutil ~=5.4',
'python-slugify >=1.2,<1.3', 'python-slugify ~=1.2.0',
'requests >=2,<3', 'requests ~=2.0',
'stevedore ~=1.27', 'stevedore ~=1.27',
'whistle >=1.0,<1.1', 'whistle ~=1.0',
dev=[ dev=[
'pytest-sugar >=0.9,<0.10', 'pytest-sugar >=0.9,<0.10',
'pytest-timeout >=1,<2', 'pytest-timeout ~=1.0',
], ],
docker=[ docker=[
'bonobo-docker >=0.5.0', 'bonobo-docker',
], ],
jupyter=[ jupyter=[
'ipywidgets >=6.0.0,<7', 'ipywidgets ~=6.0',
'jupyter >=1.0,<1.1', 'jupyter ~=1.0',
], ],
sqlalchemy=[ sqlalchemy=[
'bonobo-sqlalchemy >=0.5.1', 'bonobo-sqlalchemy',
], ],
) )
# Following requirements are not enforced, because some dependencies enforce them so we don't want to break
# the packaging in case it changes in dep.
python.add_requirements('colorama >=0.3')
# vim: ft=python: # vim: ft=python:

View File

@ -3,45 +3,15 @@ from bonobo.nodes import __all__ as _all_nodes
from bonobo.nodes import * from bonobo.nodes import *
from bonobo.structs import Graph from bonobo.structs import Graph
from bonobo.util import get_name from bonobo.util import get_name
from bonobo.util.api import ApiHelper
from bonobo.util.environ import parse_args, get_argument_parser from bonobo.util.environ import parse_args, get_argument_parser
__all__ = [] __all__ = []
api = ApiHelper(__all__)
def register_api(x, __all__=__all__):
"""Register a function as being part of Bonobo's API, then returns the original function."""
__all__.append(get_name(x))
return x
def register_graph_api(x, __all__=__all__): @api.register_graph
"""
Register a function as being part of Bonobo's API, after checking that its signature contains the right parameters
to work correctly, then returns the original function.
"""
from inspect import signature
parameters = list(signature(x).parameters)
required_parameters = {'plugins', 'services', 'strategy'}
assert parameters[0] == 'graph', 'First parameter of a graph api function must be "graph".'
assert required_parameters.intersection(
parameters
) == required_parameters, 'Graph api functions must define the following parameters: ' + ', '.join(
sorted(required_parameters)
)
return register_api(x, __all__=__all__)
def register_api_group(*args, check=None):
check = set(check) if check else None
for attr in args:
register_api(attr)
if check:
check.remove(get_name(attr))
assert not (check and len(check))
@register_graph_api
def run(graph, *, plugins=None, services=None, strategy=None): def run(graph, *, plugins=None, services=None, strategy=None):
""" """
Main entry point of bonobo. It takes a graph and creates all the necessary plumbing around to execute it. Main entry point of bonobo. It takes a graph and creates all the necessary plumbing around to execute it.
@ -104,7 +74,7 @@ def _inspect_as_graph(graph):
_inspect_formats = {'graph': _inspect_as_graph} _inspect_formats = {'graph': _inspect_as_graph}
@register_graph_api @api.register_graph
def inspect(graph, *, plugins=None, services=None, strategy=None, format): def inspect(graph, *, plugins=None, services=None, strategy=None, format):
if not format in _inspect_formats: if not format in _inspect_formats:
raise NotImplementedError( raise NotImplementedError(
@ -116,14 +86,14 @@ def inspect(graph, *, plugins=None, services=None, strategy=None, format):
# data structures # data structures
register_api_group(Graph) api.register_group(Graph)
# execution strategies # execution strategies
register_api(create_strategy) api.register_group(create_strategy)
# Shortcut to filesystem2's open_fs, that we make available there for convenience. # Shortcut to filesystem2's open_fs, that we make available there for convenience.
@register_api @api.register
def open_fs(fs_url=None, *args, **kwargs): def open_fs(fs_url=None, *args, **kwargs):
""" """
Wraps :func:`fs.open_fs` function with a few candies. Wraps :func:`fs.open_fs` function with a few candies.
@ -148,7 +118,7 @@ def open_fs(fs_url=None, *args, **kwargs):
# standard transformations # standard transformations
register_api_group( api.register_group(
CsvReader, CsvReader,
CsvWriter, CsvWriter,
FileReader, FileReader,
@ -189,16 +159,16 @@ def _is_jupyter_notebook():
return False return False
@register_api @api.register
def get_examples_path(*pathsegments): def get_examples_path(*pathsegments):
import os import os
import pathlib import pathlib
return str(pathlib.Path(os.path.dirname(__file__), 'examples', *pathsegments)) return str(pathlib.Path(os.path.dirname(__file__), 'examples', *pathsegments))
@register_api @api.register
def open_examples_fs(*pathsegments): def open_examples_fs(*pathsegments):
return open_fs(get_examples_path(*pathsegments)) return open_fs(get_examples_path(*pathsegments))
register_api_group(get_argument_parser, parse_args) api.register_group(get_argument_parser, parse_args)

35
bonobo/util/api.py Normal file
View File

@ -0,0 +1,35 @@
from bonobo.util import get_name
class ApiHelper:
def __init__(self, __all__):
self.__all__ = __all__
def register(self, x, graph=False):
"""Register a function as being part of an API, then returns the original function."""
if graph:
# This function must comply to the "graph" API interface, meaning it can bahave like bonobo.run.
from inspect import signature
parameters = list(signature(x).parameters)
required_parameters = {'plugins', 'services', 'strategy'}
assert parameters[0] == 'graph', 'First parameter of a graph api function must be "graph".'
assert required_parameters.intersection(
parameters
) == required_parameters, 'Graph api functions must define the following parameters: ' + ', '.join(
sorted(required_parameters)
)
self.__all__.append(get_name(x))
return x
def register_graph(self, x):
return self.register(x, graph=True)
def register_group(self, *args, check=None):
check = set(check) if check else None
for attr in args:
self.register(attr)
if check:
check.remove(get_name(attr))
assert not (check and len(check))

View File

@ -31,9 +31,6 @@ We'll create a job to do the following
* Display it (in the next step, we'll learn about writing the result to a file. * Display it (in the next step, we'll learn about writing the result to a file.
Moving forward Moving forward
:::::::::::::: ::::::::::::::

View File

@ -1,9 +1,6 @@
Part 4: Services and Configurables Part 4: Services and Configurables
================================== ==================================
.. note::
This section lacks completeness, sorry for that (but you can still read it!).
In the last section, we used a few new tools. In the last section, we used a few new tools.

View File

@ -1,7 +1,20 @@
Part 5: Projects and Packaging Part 5: Projects and Packaging
============================== ==============================
Until then, we worked with one file managing a job. But real life is about set of jobs working together within a project. Until then, we worked with one file managing a job.
Real life often involves more complicated setups, with relations and imports between different files.
This section will describe the options available to move this file into a package, either a new one or something
that already exists in your own project.
Data processing is something a wide variety of tools may want to include, and thus |bonobo| does not enforce any
kind of project structure, as the targert structure will be dicated by the hosting project. For example, a `pipelines`
sub-package would perfectly fit a django or flask project, or even a regular package, but it's up to you to chose the
structure of your project.
about using |bonobo| in a pyt
is about set of jobs working together within a project.
Let's see how to move from the current status to a package. Let's see how to move from the current status to a package.

View File

@ -1,11 +0,0 @@
Just enough Python for Bonobo
=============================
.. todo::
This is a work in progress and it is not yet available. Please come back later or even better, help us write this
guide!
This guide is intended to help programmers or enthusiasts to grasp the python basics necessary to use Bonobo. It
should definately not be considered as a general python introduction, neither a deep dive into details.

View File

@ -20,8 +20,8 @@ python-slugify==1.2.4
pytz==2017.3 pytz==2017.3
requests==2.18.4 requests==2.18.4
six==1.11.0 six==1.11.0
stevedore==1.27.1 stevedore==1.28.0
unidecode==0.4.21 unidecode==0.4.21
urllib3==1.22 urllib3==1.22
websocket-client==0.44.0 websocket-client==0.45.0
whistle==1.0.0 whistle==1.0.0

View File

@ -1,5 +1,6 @@
-e .[jupyter] -e .[jupyter]
appnope==0.1.0 appnope==0.1.0
attrs==17.3.0
bleach==2.1.2 bleach==2.1.2
decorator==4.1.2 decorator==4.1.2
entrypoints==0.2.3 entrypoints==0.2.3
@ -8,10 +9,10 @@ ipykernel==4.7.0
ipython-genutils==0.2.0 ipython-genutils==0.2.0
ipython==6.2.1 ipython==6.2.1
ipywidgets==6.0.1 ipywidgets==6.0.1
jedi==0.11.0 jedi==0.11.1
jinja2==2.10 jinja2==2.10
jsonschema==2.6.0 jsonschema==2.6.0
jupyter-client==5.1.0 jupyter-client==5.2.0
jupyter-console==5.2.0 jupyter-console==5.2.0
jupyter-core==4.4.0 jupyter-core==4.4.0
jupyter==1.0.0 jupyter==1.0.0
@ -21,12 +22,15 @@ nbconvert==5.3.1
nbformat==4.4.0 nbformat==4.4.0
notebook==5.2.2 notebook==5.2.2
pandocfilters==1.4.2 pandocfilters==1.4.2
parso==0.1.0 parso==0.1.1
pexpect==4.3.1 pexpect==4.3.1
pickleshare==0.7.4 pickleshare==0.7.4
pluggy==0.6.0
prompt-toolkit==1.0.15 prompt-toolkit==1.0.15
ptyprocess==0.5.2 ptyprocess==0.5.2
py==1.5.2
pygments==2.2.0 pygments==2.2.0
pytest==3.3.1
python-dateutil==2.6.1 python-dateutil==2.6.1
pyzmq==17.0.0b3 pyzmq==17.0.0b3
qtconsole==4.3.1 qtconsole==4.3.1

View File

@ -19,7 +19,7 @@ pytz==2017.3
requests==2.18.4 requests==2.18.4
six==1.11.0 six==1.11.0
sqlalchemy==1.1.15 sqlalchemy==1.1.15
stevedore==1.27.1 stevedore==1.28.0
unidecode==0.4.21 unidecode==0.4.21
urllib3==1.22 urllib3==1.22
whistle==1.0.0 whistle==1.0.0

View File

@ -59,18 +59,17 @@ setup(
packages=find_packages(exclude=['ez_setup', 'example', 'test']), packages=find_packages(exclude=['ez_setup', 'example', 'test']),
include_package_data=True, include_package_data=True,
install_requires=[ install_requires=[
'colorama (>= 0.3)', 'fs (>= 2.0, < 2.1)', 'graphviz (>= 0.8, < 0.9)', 'jinja2 (>= 2.9, < 3)', 'fs (~= 2.0)', 'graphviz (>= 0.8, < 0.9)', 'jinja2 (~= 2.9)', 'mondrian (~= 0.6)', 'packaging (~= 16.0)',
'mondrian (>= 0.6, < 0.7)', 'packaging (>= 16, < 17)', 'psutil (>= 5.4, < 6)', 'python-slugify (>= 1.2, < 1.3)', 'psutil (~= 5.4)', 'python-slugify (~= 1.2.0)', 'requests (~= 2.0)', 'stevedore (~= 1.27)', 'whistle (~= 1.0)'
'requests (>= 2, < 3)', 'stevedore (~= 1.27)', 'whistle (>= 1.0, < 1.1)'
], ],
extras_require={ extras_require={
'dev': [ 'dev': [
'coverage (>= 4.4, < 5.0)', 'pytest (>= 3.1, < 4.0)', 'pytest-cov (>= 2.5, < 3.0)', 'coverage (>= 4.4, < 5.0)', 'pytest (>= 3.1, < 4.0)', 'pytest-cov (>= 2.5, < 3.0)',
'pytest-sugar (>= 0.9, < 0.10)', 'pytest-timeout (>= 1, < 2)', 'sphinx (>= 1.6, < 2.0)', 'yapf' 'pytest-sugar (>= 0.9, < 0.10)', 'pytest-timeout (~= 1.0)', 'sphinx (>= 1.6, < 2.0)', 'yapf'
], ],
'docker': ['bonobo-docker (>= 0.5.0)'], 'docker': ['bonobo-docker'],
'jupyter': ['ipywidgets (>= 6.0.0, < 7)', 'jupyter (>= 1.0, < 1.1)'], 'jupyter': ['ipywidgets (~= 6.0)', 'jupyter (~= 1.0)'],
'sqlalchemy': ['bonobo-sqlalchemy (>= 0.5.1)'] 'sqlalchemy': ['bonobo-sqlalchemy']
}, },
entry_points={ entry_points={
'bonobo.commands': [ 'bonobo.commands': [