Skip to content
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

Add python conf to the metadata #894

Merged
merged 45 commits into from
Mar 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
592ca36
add core types and type checking function
gaoning777 Mar 1, 2019
3a3fc17
fix unit test bug
gaoning777 Mar 1, 2019
db45632
avoid defining dynamic classes
gaoning777 Mar 1, 2019
dcc3baf
typo fix
gaoning777 Mar 1, 2019
89c0907
add component metadata format
gaoning777 Mar 1, 2019
e55515e
add a construct for the component decorator
gaoning777 Mar 1, 2019
eed41fa
add default values for the meta classes
gaoning777 Mar 1, 2019
d74d4d8
Merge branch 'add-component-metadata' into add-python-meta
gaoning777 Mar 1, 2019
18d5459
add input/output types to the metadata
gaoning777 Mar 1, 2019
9218ffe
add from_dict in TypeMeta
gaoning777 Mar 1, 2019
d28d555
Merge branch 'add-component-metadata' into add-python-meta
gaoning777 Mar 1, 2019
aac35b1
small fix
gaoning777 Mar 1, 2019
5ccc857
add unit tests
gaoning777 Mar 1, 2019
3081d47
use python struct for the openapi schema
gaoning777 Mar 1, 2019
dcf2ecc
Merge branch 'add-core-types-and-checking' into add-component-metadata
gaoning777 Mar 1, 2019
a2c89de
Merge branch 'add-component-metadata' into add-python-meta
gaoning777 Mar 1, 2019
b0a7314
add default in parameter
gaoning777 Mar 1, 2019
c51ccf7
Merge branch 'add-component-metadata' into add-python-meta
gaoning777 Mar 1, 2019
b69b807
add default value
gaoning777 Mar 1, 2019
57232e4
remove the str restriction for the param default
gaoning777 Mar 1, 2019
eb7462a
Merge branch 'add-component-metadata' into add-python-meta
gaoning777 Mar 1, 2019
04b86f2
bug fix
gaoning777 Mar 1, 2019
622ad68
add pipelinemeta
gaoning777 Mar 1, 2019
8834507
Merge branch 'add-component-metadata' into add-python-meta
gaoning777 Mar 1, 2019
ac49e68
add pipeline metadata
gaoning777 Mar 2, 2019
e05e5e9
ignore annotation if it is not str/BaseType/dict
gaoning777 Mar 2, 2019
10ceb09
update param name in the check_type functions
gaoning777 Mar 4, 2019
6084865
Merge branch 'add-core-types-and-checking' into add-component-metadata
gaoning777 Mar 4, 2019
987e22f
remove default values for non-primitive types in the function signature
gaoning777 Mar 4, 2019
a37084b
Merge branch 'add-component-metadata' into add-python-meta
gaoning777 Mar 4, 2019
3d625b3
typo in the comments
gaoning777 Mar 5, 2019
dd2601f
Merge branch 'master' into add-component-metadata
gaoning777 Mar 5, 2019
03c09d9
Merge branch 'master' into add-component-metadata
gaoning777 Mar 5, 2019
9c233b3
move the metadata classes to a separate module
gaoning777 Mar 5, 2019
ad66a1a
Merge branch 'master' into add-python-meta
gaoning777 Mar 5, 2019
25f6b33
Merge branch 'add-component-metadata' into add-python-meta
gaoning777 Mar 5, 2019
8ddfbb2
fix unit test
gaoning777 Mar 5, 2019
637b921
add __eq__ to meta classes
gaoning777 Mar 5, 2019
40224da
Merge branch 'add-component-metadata' into add-python-meta
gaoning777 Mar 5, 2019
2392815
fix unit test
gaoning777 Mar 5, 2019
1621f0c
Merge branch 'add-component-metadata' into add-python-meta
gaoning777 Mar 5, 2019
8312845
fix bug: duplicate variable of args
gaoning777 Mar 5, 2019
7bf701a
move python_component and _component decorator in _component file
gaoning777 Mar 5, 2019
094cf77
Merge branch 'master' into add-python-meta
gaoning777 Mar 6, 2019
16c2f8f
remove the print
gaoning777 Mar 6, 2019
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
3 changes: 2 additions & 1 deletion sdk/python/kfp/dsl/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@
from ._pipeline import Pipeline, pipeline, get_pipeline_conf
from ._container_op import ContainerOp
from ._ops_group import OpsGroup, ExitHandler, Condition
from ._python_component import python_component
from ._component import python_component
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: do you need to expose component annotation here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to expose the component decorator when fully tested. WDYT?

#TODO: expose the component decorator when ready
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from ._metadata import ComponentMeta, ParameterMeta, TypeMeta, _annotation_to_typemeta

def python_component(name, description=None, base_image=None, target_component_file: str = None):
"""Decorator for Python component functions.
This decorator adds the metadata to the function object itself.
Expand Down Expand Up @@ -47,3 +49,50 @@ def _python_component(func):
return func

return _python_component

def component(func):
"""Decorator for component functions that use ContainerOp.
This is useful to enable type checking in the DSL compiler

Usage:
```python
@dsl.component
def foobar(model: TFModel(), step: MLStep()):
return dsl.ContainerOp()
"""
def _component(*args, **kargs):
import inspect
fullargspec = inspect.getfullargspec(func)
annotations = fullargspec.annotations

# defaults
arg_defaults = {}
if fullargspec.defaults:
for arg, default in zip(reversed(fullargspec.args), reversed(fullargspec.defaults)):
arg_defaults[arg] = default

# Construct the ComponentMeta
component_meta = ComponentMeta(name=func.__name__, description='')
# Inputs
for arg in fullargspec.args:
arg_type = TypeMeta()
arg_default = arg_defaults[arg] if arg in arg_defaults else ''
if arg in annotations:
arg_type = _annotation_to_typemeta(annotations[arg])
component_meta.inputs.append(ParameterMeta(name=arg, description='', param_type=arg_type, default=arg_default))
# Outputs
for output in annotations['return']:
arg_type = _annotation_to_typemeta(annotations['return'][output])
component_meta.outputs.append(ParameterMeta(name=output, description='', param_type=arg_type))

#TODO: add descriptions to the metadata
#docstring parser:
# https://github.com/rr-/docstring_parser
# https://github.com/terrencepreilly/darglint/blob/master/darglint/parse.py

print(component_meta.serialize())
#TODO: parse the metadata to the ContainerOp.
container_op = func(*args, **kargs)
return container_op

return _component
23 changes: 21 additions & 2 deletions sdk/python/kfp/dsl/_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

from typing import Dict, List
from abc import ABCMeta, abstractmethod
from ._types import _check_valid_type_dict
from ._types import BaseType, _check_valid_type_dict, _str_to_dict, _instance_to_dict

class BaseMeta(object):
__metaclass__ = ABCMeta
Expand Down Expand Up @@ -104,4 +104,23 @@ def to_dict(self):
return {'name': self.name,
'description': self.description,
'inputs': [ input.to_dict() for input in self.inputs ]
}
}

def _annotation_to_typemeta(annotation):
'''_annotation_to_type_meta converts an annotation to an instance of TypeMeta
Args:
annotation(BaseType/str/dict): input/output annotations
Returns:
TypeMeta
'''
if isinstance(annotation, BaseType):
arg_type = TypeMeta.from_dict(_instance_to_dict(annotation))
elif isinstance(annotation, str):
arg_type = TypeMeta.from_dict(_str_to_dict(annotation))
elif isinstance(annotation, dict):
if not _check_valid_type_dict(annotation):
raise ValueError('Annotation ' + str(annotation) + ' is not a valid type dictionary.')
arg_type = TypeMeta.from_dict(annotation)
else:
return TypeMeta()
return arg_type
21 changes: 21 additions & 0 deletions sdk/python/kfp/dsl/_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@


from . import _container_op
from ._metadata import PipelineMeta, ParameterMeta, TypeMeta, _annotation_to_typemeta
from . import _ops_group
import sys

Expand All @@ -32,6 +33,26 @@ def my_pipeline(a: PipelineParam, b: PipelineParam):
```
"""
def _pipeline(func):
import inspect
fullargspec = inspect.getfullargspec(func)
args = fullargspec.args
annotations = fullargspec.annotations

# Construct the PipelineMeta
pipeline_meta = PipelineMeta(name=func.__name__, description='')
# Inputs
for arg in args:
arg_type = TypeMeta()
if arg in annotations:
arg_type = _annotation_to_typemeta(annotations[arg])
pipeline_meta.inputs.append(ParameterMeta(name=arg, description='', param_type=arg_type))

#TODO: add descriptions to the metadata
#docstring parser:
# https://github.com/rr-/docstring_parser
# https://github.com/terrencepreilly/darglint/blob/master/darglint/parse.py
#TODO: parse the metadata to the Pipeline.

Pipeline.add_pipeline(name, description, func)
return func

Expand Down
28 changes: 28 additions & 0 deletions sdk/python/tests/dsl/component_tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Copyright 2018 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


from kfp.dsl._component import component
from kfp.dsl._types import GCSPath, Integer
import unittest

@component
def componentA(a: {'Schema': {'file_type': 'csv'}}, b: '{"number": {"step": "large"}}' = 12, c: GCSPath(path_type='file', file_type='tsv') = 'gs://hello/world') -> {'model': Integer()}:
return 7

class TestPythonComponent(unittest.TestCase):

def test_component(self):
"""Test component decorator."""
componentA(1,2,3)
2 changes: 2 additions & 0 deletions sdk/python/tests/dsl/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import container_op_tests
import ops_group_tests
import type_tests
import component_tests
import metadata_tests

if __name__ == '__main__':
Expand All @@ -30,6 +31,7 @@
suite.addTests(unittest.defaultTestLoader.loadTestsFromModule(container_op_tests))
suite.addTests(unittest.defaultTestLoader.loadTestsFromModule(ops_group_tests))
suite.addTests(unittest.defaultTestLoader.loadTestsFromModule(type_tests))
suite.addTests(unittest.defaultTestLoader.loadTestsFromModule(component_tests))
suite.addTests(unittest.defaultTestLoader.loadTestsFromModule(metadata_tests))
runner = unittest.TextTestRunner()
if not runner.run(suite).wasSuccessful():
Expand Down
12 changes: 12 additions & 0 deletions sdk/python/tests/dsl/pipeline_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@


from kfp.dsl import Pipeline, PipelineParam, ContainerOp, pipeline
from kfp.dsl._types import GCSPath, Integer
import unittest


Expand Down Expand Up @@ -55,3 +56,14 @@ def my_pipeline2():

self.assertEqual(('p1', 'description1'), Pipeline.get_pipeline_functions()[my_pipeline1])
self.assertEqual(('p2', 'description2'), Pipeline.get_pipeline_functions()[my_pipeline2])

def test_decorator_metadata(self):
"""Test @pipeline decorator with metadata."""
@pipeline(
name='p1',
description='description1'
)
def my_pipeline1(a: {'Schema': {'file_type': 'csv'}}='good', b: Integer()=12):
pass

self.assertEqual(('p1', 'description1'), Pipeline.get_pipeline_functions()[my_pipeline1])
gaoning777 marked this conversation as resolved.
Show resolved Hide resolved