Skip to content

Commit 34ea115

Browse files
authored
Add new validate in python (#4982)
1 parent a97a8d0 commit 34ea115

File tree

2 files changed

+61
-65
lines changed

2 files changed

+61
-65
lines changed

cli/module_generate/_templates/python/src/models/tmpl-resource.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import ClassVar, Mapping, Sequence, Self
1+
from typing import ClassVar, Mapping, Sequence, Self, Tuple
22
from viam.proto.app.robot import ComponentConfig
33
from viam.proto.common import ResourceName
44
from viam.resource.base import ResourceBase
@@ -25,17 +25,19 @@ def new(cls, config: ComponentConfig, dependencies: Mapping[ResourceName, Resour
2525
return super().new(config, dependencies)
2626

2727
@classmethod
28-
def validate_config(cls, config: ComponentConfig) -> Sequence[str]:
28+
def validate_config(cls, config: ComponentConfig) -> Tuple[Sequence[str], Sequence[str]]:
2929
"""This method allows you to validate the configuration object received from the machine,
30-
as well as to return any implicit dependencies based on that `config`.
30+
as well as to return any required dependencies or optional dependencies based on that `config`.
3131
3232
Args:
3333
config (ComponentConfig): The configuration for this resource
3434
3535
Returns:
36-
Sequence[str]: A list of implicit dependencies
36+
Tuple[Sequence[str], Sequence[str]]: A tuple where the
37+
first element is a list of required dependencies and the
38+
second element is a list of optional dependencies
3739
"""
38-
return []
40+
return [], []
3941

4042
def reconfigure(self, config: ComponentConfig, dependencies: Mapping[ResourceName, ResourceBase]):
4143
"""This method allows you to dynamically update your service when it receives a new `config` object.

cli/module_generate/scripts/generate_stubs.py

Lines changed: 54 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,15 @@ def return_attribute(value: str, attr: str) -> ast.Attribute:
1010
return ast.Attribute(
1111
value=ast.Name(id=value, ctx=ast.Load()),
1212
attr=attr,
13-
ctx=ast.Load())
13+
ctx=ast.Load(),
14+
)
1415

1516

1617
def update_annotation(
1718
resource_name: str,
1819
annotation: Union[ast.Name, ast.Subscript],
1920
nodes: Set[str],
20-
parent: str
21+
parent: str,
2122
) -> Union[ast.Attribute, ast.Subscript]:
2223
if isinstance(annotation, ast.Name) and annotation.id in nodes:
2324
value = parent if parent else resource_name
@@ -27,48 +28,43 @@ def update_annotation(
2728
resource_name,
2829
annotation.slice,
2930
nodes,
30-
parent)
31+
parent,
32+
)
3133
return annotation
3234

3335

3436
def replace_async_func(
35-
resource_name: str,
36-
func: ast.AsyncFunctionDef,
37-
nodes: Set[str],
38-
parent: str = ""
37+
resource_name: str, func: ast.AsyncFunctionDef, nodes: Set[str], parent: str = ""
3938
) -> None:
4039
for arg in func.args.args:
41-
arg.annotation = update_annotation(
42-
resource_name,
43-
arg.annotation,
44-
nodes,
45-
parent)
40+
arg.annotation = update_annotation(resource_name, arg.annotation, nodes, parent)
4641
func.body = [
47-
ast.Expr(ast.Call(
48-
func=ast.Attribute(
49-
value=ast.Attribute(
50-
ast.Name(id="self"),
51-
attr="logger"),
52-
attr="error",
53-
),
54-
args=[ast.Constant(value=f"`{func.name}` is not implemented")],
55-
keywords=[])),
42+
ast.Expr(
43+
ast.Call(
44+
func=ast.Attribute(
45+
value=ast.Attribute(ast.Name(id="self"), attr="logger"),
46+
attr="error",
47+
),
48+
args=[ast.Constant(value=f"`{func.name}` is not implemented")],
49+
keywords=[],
50+
)
51+
),
5652
ast.Raise(
57-
exc=ast.Call(func=ast.Name(id='NotImplementedError',
58-
ctx=ast.Load()),
59-
args=[],
60-
keywords=[]),
61-
cause=None)
53+
exc=ast.Call(
54+
func=ast.Name(id="NotImplementedError", ctx=ast.Load()),
55+
args=[],
56+
keywords=[],
57+
),
58+
cause=None,
59+
),
6260
]
6361
func.decorator_list = []
6462
if isinstance(func.returns, (ast.Name, ast.Subscript)):
65-
func.returns = update_annotation(
66-
resource_name, func.returns, nodes, parent
67-
)
63+
func.returns = update_annotation(resource_name, func.returns, nodes, parent)
6864

6965

7066
def return_subclass(
71-
resource_name: str, stmt: ast.ClassDef, parent: str = ""
67+
resource_name: str, stmt: ast.ClassDef, parent: str = ""
7268
) -> List[str]:
7369
def parse_subclass(resource_name: str, stmt: ast.ClassDef, parent: str):
7470
nodes = set()
@@ -93,9 +89,7 @@ def parse_subclass(resource_name: str, stmt: ast.ClassDef, parent: str):
9389
stmt.body = [ast.Pass()]
9490

9591
parse_subclass(resource_name, stmt, parent)
96-
return '\n'.join(
97-
[' ' + line for line in ast.unparse(stmt).splitlines()]
98-
)
92+
return "\n".join([" " + line for line in ast.unparse(stmt).splitlines()])
9993

10094

10195
def main(
@@ -108,14 +102,12 @@ def main(
108102
import isort
109103
from slugify import slugify
110104

111-
module_name = (
112-
f"viam.{resource_type}s.{resource_subtype}.{resource_subtype}"
113-
)
105+
module_name = f"viam.{resource_type}s.{resource_subtype}.{resource_subtype}"
114106
module = import_module(module_name)
115-
resource_name = {
116-
"input": "Controller", "slam": "SLAM", "mlmodel": "MLModel"
117-
}.get(resource_subtype, "".join(word.capitalize()
118-
for word in resource_subtype.split("_")))
107+
resource_name = {"input": "Controller", "slam": "SLAM", "mlmodel": "MLModel"}.get(
108+
resource_subtype,
109+
"".join(word.capitalize() for word in resource_subtype.split("_")),
110+
)
119111

120112
imports, subclasses, abstract_methods = [], [], []
121113
nodes = set()
@@ -132,8 +124,11 @@ def main(
132124
for imp in stmt.names:
133125
if imp.name in modules_to_ignore:
134126
continue
135-
imports.append(f"import {imp.name} as {imp.asname}"
136-
if imp.asname else f"import {imp.name}")
127+
imports.append(
128+
f"import {imp.name} as {imp.asname}"
129+
if imp.asname
130+
else f"import {imp.name}"
131+
)
137132
elif (
138133
isinstance(stmt, ast.ImportFrom)
139134
and stmt.module
@@ -159,8 +154,8 @@ def main(
159154
nodes.add(cstmt.target.id)
160155
elif isinstance(cstmt, ast.AsyncFunctionDef):
161156
replace_async_func(resource_name, cstmt, nodes)
162-
indented_code = '\n'.join(
163-
[' ' + line for line in ast.unparse(cstmt).splitlines()]
157+
indented_code = "\n".join(
158+
[" " + line for line in ast.unparse(cstmt).splitlines()]
164159
)
165160
abstract_methods.append(indented_code)
166161

@@ -172,22 +167,24 @@ def main(
172167
for cstmt in stmt.body:
173168
if isinstance(cstmt, ast.AsyncFunctionDef):
174169
replace_async_func("", cstmt, [])
175-
indented_code = '\n'.join(
176-
[' ' + line for line in ast.unparse(cstmt).splitlines()]
170+
indented_code = "\n".join(
171+
[" " + line for line in ast.unparse(cstmt).splitlines()]
177172
)
178173
abstract_methods.append(indented_code)
179174
if cstmt.name == "do_command":
180175
imports.append("from typing import Optional")
181176
imports.append("from viam.utils import ValueTypes")
182177
elif cstmt.name == "get_geometries":
183-
imports.append("from typing import Any, Dict, List, Optional")
178+
imports.append(
179+
"from typing import Any, Dict, List, Optional"
180+
)
184181
imports.append("from viam.proto.common import Geometry")
185182

186183
model_name_pascal = "".join(
187184
[word.capitalize() for word in slugify(model_name).split("-")]
188185
)
189186
resource_file = '''
190-
from typing import ClassVar, Mapping, Sequence
187+
from typing import ClassVar, Mapping, Sequence, Tuple
191188
from typing_extensions import Self
192189
from viam.proto.app.robot import ComponentConfig
193190
from viam.proto.common import ResourceName
@@ -218,17 +215,19 @@ def new(cls, config: ComponentConfig, dependencies: Mapping[ResourceName, Resour
218215
return super().new(config, dependencies)
219216
220217
@classmethod
221-
def validate_config(cls, config: ComponentConfig) -> Sequence[str]:
218+
def validate_config(cls, config: ComponentConfig) -> Tuple[Sequence[str], Sequence[str]]:
222219
"""This method allows you to validate the configuration object received from the machine,
223-
as well as to return any implicit dependencies based on that `config`.
220+
as well as to return any required dependencies or optional dependencies based on that `config`.
224221
225222
Args:
226223
config (ComponentConfig): The configuration for this resource
227224
228225
Returns:
229-
Sequence[str]: A list of implicit dependencies
226+
Tuple[Sequence[str], Sequence[str]]: A tuple where the
227+
first element is a list of required dependencies and the
228+
second element is a list of optional dependencies
230229
"""
231-
return []
230+
return [], []
232231
233232
def reconfigure(self, config: ComponentConfig, dependencies: Mapping[ResourceName, ResourceBase]):
234233
"""This method allows you to dynamically update your service when it receives a new `config` object.
@@ -250,8 +249,8 @@ def reconfigure(self, config: ComponentConfig, dependencies: Mapping[ResourceNam
250249
namespace,
251250
mod_name,
252251
model_name,
253-
'\n\n'.join([subclass for subclass in subclasses]),
254-
'\n\n'.join([f'{method}' for method in abstract_methods]),
252+
"\n\n".join([subclass for subclass in subclasses]),
253+
"\n\n".join([f"{method}" for method in abstract_methods]),
255254
)
256255
f_name = os.path.join(mod_name, "src", "models", "resource.py")
257256
with open(f_name, "w+") as f:
@@ -274,12 +273,7 @@ def reconfigure(self, config: ComponentConfig, dependencies: Mapping[ResourceNam
274273
if sys.argv[2] == "mlmodel":
275274
packages.append("numpy")
276275
install_res = subprocess.run(
277-
[
278-
sys.executable,
279-
"-m",
280-
"pip",
281-
"install"
282-
] + packages,
276+
[sys.executable, "-m", "pip", "install"] + packages,
283277
capture_output=True,
284278
)
285279
if install_res.returncode != 0:

0 commit comments

Comments
 (0)