Skip to content

Commit 0692e87

Browse files
committed
make release-tag: Merge branch 'master' into stable
2 parents 3b06ab8 + b7baf96 commit 0692e87

File tree

12 files changed

+284
-88
lines changed

12 files changed

+284
-88
lines changed

CONTRIBUTING.rst

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,3 +195,33 @@ Once this is done, run of the following commands:
195195
3. If you are releasing a major version::
196196

197197
make release-major
198+
199+
Release Candidates
200+
~~~~~~~~~~~~~~~~~~
201+
202+
Sometimes it is necessary or convenient to upload a release candidate to PyPi as a pre-release,
203+
in order to make some of the new features available for testing on other projects before they
204+
are included in an actual full-blown release.
205+
206+
In order to perform such an action, you can execute::
207+
208+
make release-candidate
209+
210+
This will perform the following actions:
211+
212+
1. Build and upload the current version to PyPi as a pre-release, with the format ``X.Y.Z.devN``
213+
214+
2. Bump the current version to the next release candidate, ``X.Y.Z.dev(N+1)``
215+
216+
After this is done, the new pre-release can be installed by including the ``dev`` section in the
217+
dependency specification, either in ``setup.py``::
218+
219+
install_requires = [
220+
...
221+
'mlblocks>=X.Y.Z.dev',
222+
...
223+
]
224+
225+
or in command line::
226+
227+
pip install 'mlblocks>=X.Y.Z.dev'

HISTORY.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
Changelog
22
=========
33

4+
0.3.4 - 2019-11-01
5+
------------------
6+
7+
* Ability to return intermediate context - [Issue #110](https://github.com/HDI-Project/MLBlocks/issues/110) by @csala
8+
* Support for static or class methods - [Issue #107](https://github.com/HDI-Project/MLBlocks/issues/107) by @csala
9+
410
0.3.3 - 2019-09-09
511
------------------
612

Makefile

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ publish: dist ## package and upload a release
155155

156156
.PHONY: bumpversion-release
157157
bumpversion-release: ## Merge master to stable and bumpversion release
158-
git checkout stable
158+
git checkout stable || git checkout -b stable
159159
git merge --no-ff master -m"make release-tag: Merge branch 'master' into stable"
160160
bumpversion release
161161
git push --tags origin stable
@@ -167,6 +167,10 @@ bumpversion-patch: ## Merge stable to master and bumpversion patch
167167
bumpversion --no-tag patch
168168
git push
169169

170+
.PHONY: bumpversion-candidate
171+
bumpversion-candidate: ## Bump the version to the next candidate
172+
bumpversion candidate --no-tag
173+
170174
.PHONY: bumpversion-minor
171175
bumpversion-minor: ## Bump the version the next minor skipping the release
172176
bumpversion --no-tag minor
@@ -175,23 +179,31 @@ bumpversion-minor: ## Bump the version the next minor skipping the release
175179
bumpversion-major: ## Bump the version the next major skipping the release
176180
bumpversion --no-tag major
177181

178-
CURRENT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD)
179-
CHANGELOG_LINES := $(shell git diff HEAD..stable HISTORY.md | wc -l)
182+
CURRENT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null)
183+
CHANGELOG_LINES := $(shell git diff HEAD..origin/stable HISTORY.md 2>&1 | wc -l)
180184

181-
.PHONY: check-release
182-
check-release: ## Check if the release can be made
185+
.PHONY: check-master
186+
check-master: ## Check if we are in master branch
183187
ifneq ($(CURRENT_BRANCH),master)
184188
$(error Please make the release from master branch\n)
185189
endif
190+
191+
.PHONY: check-history
192+
check-history: ## Check if HISTORY.md has been modified
186193
ifeq ($(CHANGELOG_LINES),0)
187194
$(error Please insert the release notes in HISTORY.md before releasing)
188-
else
189-
@echo "A new release can be made"
190195
endif
191196

197+
.PHONY: check-release
198+
check-release: check-master check-history ## Check if the release can be made
199+
@echo "A new release can be made"
200+
192201
.PHONY: release
193202
release: check-release bumpversion-release publish bumpversion-patch
194203

204+
.PHONY: release-candidate
205+
release-candidate: check-master publish bumpversion-candidate
206+
195207
.PHONY: release-minor
196208
release-minor: check-release bumpversion-minor release
197209

mlblocks/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
__copyright__ = 'Copyright (c) 2018, MIT Data To AI Lab'
2121
__email__ = '[email protected]'
2222
__license__ = 'MIT'
23-
__version__ = '0.3.3'
23+
__version__ = '0.3.4.dev2'
2424

2525
__all__ = [
2626
'MLBlock', 'MLPipeline', 'add_pipelines_path', 'add_primitives_path',

mlblocks/mlblock.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,17 @@
1313

1414
def import_object(object_name):
1515
"""Import an object from its Fully Qualified Name."""
16+
1617
if isinstance(object_name, str):
17-
package, name = object_name.rsplit('.', 1)
18-
return getattr(importlib.import_module(package), name)
18+
parent_name, attribute = object_name.rsplit('.', 1)
19+
try:
20+
parent = importlib.import_module(parent_name)
21+
except ImportError:
22+
grand_parent_name, parent_name = parent_name.rsplit('.', 1)
23+
grand_parent = importlib.import_module(grand_parent_name)
24+
parent = getattr(grand_parent, parent_name)
25+
26+
return getattr(parent, attribute)
1927

2028
return object_name
2129

mlblocks/mlpipeline.py

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import json
66
import logging
7+
import re
78
from collections import Counter, OrderedDict, defaultdict
89
from copy import deepcopy
910

@@ -145,8 +146,11 @@ def _get_block_outputs(self, block_name):
145146
"""Get the list of output variables for the given block."""
146147
block = self.blocks[block_name]
147148
outputs = deepcopy(block.produce_output)
149+
output_names = self.output_names.get(block_name, dict())
148150
for output in outputs:
149-
output['variable'] = '{}.{}'.format(block_name, output['name'])
151+
name = output['name']
152+
context_name = output_names.get(name, name)
153+
output['variable'] = '{}.{}'.format(block_name, context_name)
150154

151155
return outputs
152156

@@ -195,12 +199,15 @@ def __init__(self, pipeline=None, primitives=None, init_params=None,
195199
if hyperparameters:
196200
self.set_hyperparameters(hyperparameters)
197201

202+
self._re_block_name = re.compile(r'(^[^#]+#\d+)(\..*)?')
203+
198204
def _get_str_output(self, output):
199205
"""Get the outputs that correspond to the str specification."""
200206
if output in self.outputs:
201207
return self.outputs[output]
202208
elif output in self.blocks:
203-
return self._get_block_outputs(output)
209+
return [{'name': output, 'variable': output}]
210+
# return self._get_block_outputs(output)
204211
elif '.' in output:
205212
block_name, variable_name = output.rsplit('.', 1)
206213
block = self.blocks.get(block_name)
@@ -257,11 +264,11 @@ def get_outputs(self, outputs='default'):
257264

258265
computed = list()
259266
for output in outputs:
267+
if isinstance(output, int):
268+
output = self._get_block_name(output)
269+
260270
if isinstance(output, str):
261271
computed.extend(self._get_str_output(output))
262-
elif isinstance(output, int):
263-
block_name = self._get_block_name(output)
264-
computed.extend(self._get_block_outputs(block_name))
265272
else:
266273
raise TypeError('Output Specification can only be str or int')
267274

@@ -313,6 +320,18 @@ def get_output_variables(self, outputs='default'):
313320
outputs = self.get_outputs(outputs)
314321
return [output['variable'] for output in outputs]
315322

323+
def _extract_block_name(self, variable_name):
324+
return self._re_block_name.search(variable_name).group(1)
325+
326+
def _prepare_outputs(self, outputs):
327+
output_variables = self.get_output_variables(outputs)
328+
outputs = output_variables.copy()
329+
output_blocks = {
330+
self._extract_block_name(variable)
331+
for variable in output_variables
332+
}
333+
return output_variables, outputs, output_blocks
334+
316335
@staticmethod
317336
def _flatten_dict(hyperparameters):
318337
return {
@@ -516,13 +535,11 @@ def _extract_outputs(self, block_name, outputs, block_outputs):
516535

517536
return output_dict
518537

519-
def _update_outputs(self, block_name, output_variables, outputs, outputs_dict):
538+
def _update_outputs(self, variable_name, output_variables, outputs, value):
520539
"""Set the requested block outputs into the outputs list in the right place."""
521-
for key, value in outputs_dict.items():
522-
variable_name = '{}.{}'.format(block_name, key)
523-
if variable_name in output_variables:
524-
index = output_variables.index(variable_name)
525-
outputs[index] = deepcopy(value)
540+
if variable_name in output_variables:
541+
index = output_variables.index(variable_name)
542+
outputs[index] = deepcopy(value)
526543

527544
def _fit_block(self, block, block_name, context):
528545
"""Get the block args from the context and fit the block."""
@@ -551,7 +568,12 @@ def _produce_block(self, block, block_name, context, output_variables, outputs):
551568
context.update(outputs_dict)
552569

553570
if output_variables:
554-
self._update_outputs(block_name, output_variables, outputs, outputs_dict)
571+
if block_name in output_variables:
572+
self._update_outputs(block_name, output_variables, outputs, context)
573+
else:
574+
for key, value in outputs_dict.items():
575+
variable_name = '{}.{}'.format(block_name, key)
576+
self._update_outputs(variable_name, output_variables, outputs, value)
555577

556578
except Exception:
557579
if self.verbose:
@@ -606,14 +628,12 @@ def fit(self, X=None, y=None, output_=None, start_=None, **kwargs):
606628
if y is not None:
607629
context['y'] = y
608630

609-
if output_ is not None:
610-
output_variables = self.get_output_variables(output_)
611-
outputs = output_variables.copy()
612-
output_blocks = {variable.rsplit('.', 1)[0] for variable in output_variables}
613-
else:
631+
if output_ is None:
614632
output_variables = None
615633
outputs = None
616634
output_blocks = set()
635+
else:
636+
output_variables, outputs, output_blocks = self._prepare_outputs(output_)
617637

618638
if isinstance(start_, int):
619639
start_ = self._get_block_name(start_)
@@ -637,7 +657,7 @@ def fit(self, X=None, y=None, output_=None, start_=None, **kwargs):
637657

638658
# If there was an output_ but there are no pending
639659
# outputs we are done.
640-
if output_ is not None and not output_blocks:
660+
if output_variables is not None and not output_blocks:
641661
if len(outputs) > 1:
642662
return tuple(outputs)
643663
else:
@@ -686,9 +706,7 @@ def predict(self, X=None, output_='default', start_=None, **kwargs):
686706
if X is not None:
687707
context['X'] = X
688708

689-
output_variables = self.get_output_variables(output_)
690-
outputs = output_variables.copy()
691-
output_blocks = {variable.rsplit('.', 1)[0] for variable in output_variables}
709+
output_variables, outputs, output_blocks = self._prepare_outputs(output_)
692710

693711
if isinstance(start_, int):
694712
start_ = self._get_block_name(start_)

setup.cfg

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
[bumpversion]
2-
current_version = 0.3.3
2+
current_version = 0.3.4.dev2
33
commit = True
44
tag = True
5-
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?
5+
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(\.(?P<release>[a-z]+)(?P<candidate>\d+))?
66
serialize =
7-
{major}.{minor}.{patch}-{release}
7+
{major}.{minor}.{patch}.{release}{candidate}
88
{major}.{minor}.{patch}
99

1010
[bumpversion:part:release]
1111
optional_value = release
12+
first_value = dev
1213
values =
1314
dev
1415
release
1516

17+
[bumpversion:part:candidate]
18+
1619
[bumpversion:file:setup.py]
1720
search = version='{current_version}'
1821
replace = version='{new_version}'
@@ -34,8 +37,12 @@ include_trailing_comment = True
3437
line_length = 99
3538
lines_between_types = 0
3639
multi_line_output = 4
37-
not_skip = __init__.py
3840
use_parentheses = True
41+
not_skip = __init__.py
42+
skip_glob = *.bak
43+
44+
[metadata]
45+
description-file = README.md
3946

4047
[aliases]
4148
test = pytest

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,6 @@
100100
test_suite='tests',
101101
tests_require=tests_require,
102102
url='https://github.com/HDI-Project/MLBlocks',
103-
version='0.3.3',
103+
version='0.3.4.dev2',
104104
zip_safe=False,
105105
)

tests/features/test_partial_outputs.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,14 @@ def test_fit_output(self):
7070
y = np.array([
7171
0, 0, 0, 0, 1
7272
])
73+
context = {'X': X, 'y': y}
7374

7475
almost_equal(named_out, y)
7576
assert len(list_out) == 2
7677
almost_equal(list_out[0], y)
77-
almost_equal(list_out[1], X)
78-
almost_equal(X, int_out)
79-
almost_equal(X, str_out)
78+
almost_equal(list_out[1], context)
79+
almost_equal(context, int_out)
80+
almost_equal(context, str_out)
8081
almost_equal(X, str_out_variable)
8182
assert no_output is None
8283

tests/test_mlblock.py

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,44 @@
33
from unittest import TestCase
44
from unittest.mock import MagicMock, Mock, patch
55

6-
from mlblocks.mlblock import MLBlock, import_object
6+
import pytest
77

8-
# import pytest
8+
from mlblocks.mlblock import MLBlock, import_object
99

1010

1111
class DummyClass:
12+
def a_method(self):
13+
pass
14+
15+
16+
def dummy_function():
1217
pass
1318

1419

15-
def test_import_object():
16-
dummy_class = import_object(__name__ + '.DummyClass')
20+
class TestImportObject(TestCase):
21+
22+
def test_class(self):
23+
imported = import_object(__name__ + '.DummyClass')
24+
25+
assert imported is DummyClass
26+
27+
def test_class_method(self):
28+
imported = import_object(__name__ + '.DummyClass.a_method')
29+
30+
assert imported is DummyClass.a_method
31+
32+
def test_function(self):
33+
imported = import_object(__name__ + '.dummy_function')
34+
35+
assert imported is dummy_function
36+
37+
def test_bad_object_name(self):
38+
with pytest.raises(AttributeError):
39+
import_object(__name__ + '.InvalidName')
1740

18-
assert dummy_class is DummyClass
41+
def test_bad_module(self):
42+
with pytest.raises(ImportError):
43+
import_object('an.invalid.module')
1944

2045

2146
class TestMLBlock(TestCase):

0 commit comments

Comments
 (0)