Skip to content

Commit e5b115e

Browse files
authored
Merge pull request #334 from hartym/develop
Docs update, new syntax.
2 parents 0d17881 + e84440d commit e5b115e

25 files changed

+598
-888
lines changed

bin/update_apidoc.py

Lines changed: 90 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88

99

1010
class Module:
11-
def __init__(self, name, title=None, *, automodule_options=None):
12-
11+
def __init__(self, name, title=None, *, automodule_options=None, append=None):
12+
self.append = append
1313
self.name = name
1414
self.title = title or " ".join(map(str.title, self.name.split(".")[1:]))
1515
self.automodule_options = automodule_options or list()
@@ -18,20 +18,101 @@ def __repr__(self):
1818
return "<{} ({})>".format(self.title, self.name)
1919

2020
def asdict(self):
21-
return {"name": self.name, "title": self.title, "automodule_options": self.automodule_options}
21+
return {
22+
"append": self.append,
23+
"automodule": True,
24+
"automodule_options": self.automodule_options,
25+
"name": self.name,
26+
"title": self.title,
27+
}
2228

2329
def get_path(self):
2430
return os.path.join(__path__, apidoc_root, *self.name.split(".")) + ".rst"
2531

2632

33+
import inspect
34+
35+
bonobo = __import__("bonobo")
36+
assert bonobo.__version__
37+
38+
prefixes = {
39+
"bonobo.nodes": None,
40+
"bonobo._api": "bonobo",
41+
"bonobo.structs.graphs": None,
42+
"bonobo.execution.strategies": "bonobo",
43+
"bonobo.registry": "bonobo",
44+
"bonobo.util.environ": "bonobo",
45+
}
46+
api_objects = {}
47+
48+
display_order = [("bonobo.structs.graphs", "Graphs"), ("bonobo.nodes", "Nodes"), ("bonobo", "Other top-level APIs")]
49+
50+
for name in sorted(dir(bonobo)):
51+
# ignore attributes starting by underscores
52+
if name.startswith("_"):
53+
continue
54+
attr = getattr(bonobo, name)
55+
if inspect.ismodule(attr):
56+
continue
57+
58+
assert name in bonobo.__all__
59+
60+
o = getattr(bonobo, name)
61+
modname = inspect.getmodule(o).__name__
62+
family = None
63+
family_override = None
64+
65+
for prefix, target in prefixes.items():
66+
if modname == prefix or modname.startswith(prefix + "."):
67+
family = target or prefix
68+
display_name = ".".join([family, name])
69+
break
70+
71+
if family is None:
72+
raise Exception("Could not find family for {}".format(name))
73+
74+
api_objects.setdefault(family, [])
75+
api_objects[family].append((name, o))
76+
77+
api_content = []
78+
current_family = None
79+
for family, title in display_order:
80+
if family != current_family:
81+
if current_family is not None:
82+
api_content.append("")
83+
api_content.append("")
84+
api_content.append(title)
85+
api_content.append(":" * len(title))
86+
api_content.append("")
87+
current_family = family
88+
89+
for api_object in sorted(api_objects[family]):
90+
object_type = "func" if inspect.isfunction(api_object[1]) else "class"
91+
api_content.append("* :{}:`{}.{}` ".format(object_type, family, api_object[0]))
92+
93+
if family == "bonobo":
94+
for api_object in sorted(api_objects[family]):
95+
object_type = "function" if inspect.isfunction(api_object[1]) else "class"
96+
api_content.append("")
97+
api_content.append("")
98+
api_content.append(api_object[0])
99+
api_content.append("-" * len(api_object[0]))
100+
api_content.append("")
101+
api_content.append(".. auto{}:: {}.{}".format(object_type, family, api_object[0]))
102+
103+
104+
print("\n".join(api_content))
105+
27106
modules = [
28-
Module("bonobo", title="Bonobo"),
107+
Module("bonobo", title="Bonobo", automodule_options=["no-members"], append="\n".join(api_content)),
29108
Module("bonobo.config"),
30109
Module("bonobo.constants", automodule_options=["no-members"]),
31110
Module("bonobo.execution"),
32111
Module("bonobo.execution.contexts"),
33112
Module("bonobo.execution.events"),
34113
Module("bonobo.execution.strategies"),
114+
Module("bonobo.nodes"),
115+
Module("bonobo.structs.graphs", title="Graphs"),
35116
Module("bonobo.util"),
36117
]
37118

@@ -50,8 +131,13 @@ def underlined_filter(txt, chr):
50131
51132
:Module: :mod:`{{ name }}`
52133
134+
{% if automodule %}
53135
.. automodule:: {{ name }}
54136
{% for opt in automodule_options %} :{{ opt }}:{{ "\n" }}{% endfor %}
137+
{% endif %}
138+
{% if append %}
139+
{{ append }}
140+
{% endif %}
55141
"""[
56142
1:-1
57143
]

bonobo/_api.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
"""
2-
Contains all the tools you need to get started with the framework, including (but not limited to) generic
3-
transformations, readers, writers, and tools for writing and executing graphs and jobs.
2+
The root :mod:`bonobo` package contains all the tools you need to get started with the framework, including (but not
3+
limited to) generic transformations, readers, writers, and tools for writing and executing graphs and jobs.
4+
5+
Mostly, it exposes objects found in sub-packages, and although you can access them directly here, you should refer to
6+
the matching documentation pages when using them.
47
58
All objects in this module are considered very safe to use, and backward compatibility when moving up from one version
69
to another is maximal.

bonobo/nodes/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
"""
2+
The :mod:`bonobo.nodes` module contains all builtin transformations that you can use out of the box in your ETL jobs.
3+
4+
Please note that all objects from this package are also available directly through the root :mod:`bonobo` package.
5+
6+
"""
7+
18
from bonobo.nodes.basics import *
29
from bonobo.nodes.basics import __all__ as _all_basics
310
from bonobo.nodes.filter import Filter

bonobo/structs/graphs.py

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,20 @@ def __rshift__(self, other):
3737
)
3838

3939
if len(nodes):
40-
chain = self.graph.add_chain(*nodes, _input=self.last)
40+
chain = self.graph.add_chain(*nodes, _input=self.last, use_existing_nodes=True)
4141
return GraphCursor(chain.graph, first=self.first, last=chain.output)
4242

4343
return self
4444

45+
def __enter__(self):
46+
return self
47+
48+
def __exit__(self, exc_type, exc_val, exc_tb):
49+
return None
50+
51+
def __eq__(self, other):
52+
return self.graph == other.graph and self.first == other.first and self.last == other.last
53+
4554

4655
class PartialGraph:
4756
def __init__(self, *nodes):
@@ -73,6 +82,15 @@ def __len__(self):
7382
def __getitem__(self, key):
7483
return self.nodes[key]
7584

85+
def __enter__(self):
86+
return self.get_cursor().__enter__()
87+
88+
def __exit__(self, exc_type, exc_val, exc_tb):
89+
return None
90+
91+
def __rshift__(self, other):
92+
return self.get_cursor().__rshift__(other)
93+
7694
def get_cursor(self, ref=BEGIN):
7795
return GraphCursor(self, last=self.index_of(ref))
7896

@@ -96,6 +114,9 @@ def index_of(self, mixed):
96114

97115
raise ValueError("Cannot find node matching {!r}.".format(mixed))
98116

117+
def indexes_of(self, *things):
118+
return set(map(self.index_of, things))
119+
99120
def outputs_of(self, idx_or_node, create=False):
100121
"""Get a set of the outputs for a given node, node index or name.
101122
"""
@@ -105,13 +126,13 @@ def outputs_of(self, idx_or_node, create=False):
105126
self.edges[idx_or_node] = set()
106127
return self.edges[idx_or_node]
107128

108-
def add_node(self, c, *, _name=None):
129+
def add_node(self, new_node, *, _name=None):
109130
"""Add a node without connections in this graph and returns its index.
110131
If _name is specified, name this node (string reference for further usage).
111132
"""
112133
idx = len(self.nodes)
113134
self.edges[idx] = set()
114-
self.nodes.append(c)
135+
self.nodes.append(new_node)
115136

116137
if _name:
117138
if _name in self.named:
@@ -120,7 +141,14 @@ def add_node(self, c, *, _name=None):
120141

121142
return idx
122143

123-
def add_chain(self, *nodes, _input=BEGIN, _output=None, _name=None):
144+
def get_or_add_node(self, new_node, *, _name=None):
145+
if new_node in self.nodes:
146+
if _name is not None:
147+
raise RuntimeError("Cannot name a node that is already present in the graph.")
148+
return self.index_of(new_node)
149+
return self.add_node(new_node, _name=_name)
150+
151+
def add_chain(self, *nodes, _input=BEGIN, _output=None, _name=None, use_existing_nodes=False):
124152
"""Add `nodes` as a chain in this graph.
125153
126154
**Input rules**
@@ -153,6 +181,8 @@ def add_chain(self, *nodes, _input=BEGIN, _output=None, _name=None):
153181
_first = None
154182
_last = None
155183

184+
get_node = self.get_or_add_node if use_existing_nodes else self.add_node
185+
156186
# Sanity checks.
157187
if not len(nodes):
158188
if _input is None or _output is None:
@@ -164,7 +194,7 @@ def add_chain(self, *nodes, _input=BEGIN, _output=None, _name=None):
164194
raise RuntimeError("Using add_chain(...) without nodes does not allow to use the _name parameter.")
165195

166196
for i, node in enumerate(nodes):
167-
_last = self.add_node(node, _name=_name if not i else None)
197+
_last = get_node(node, _name=_name if not i else None)
168198

169199
if _first is None:
170200
_first = _last

0 commit comments

Comments
 (0)