tgzr.cuisine 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,455 @@
1
+ """
2
+
3
+ Naming:
4
+ user_workspaces -> cuisine
5
+ workspace -> workbench
6
+ requirement -> input
7
+ asset -> recipe
8
+ repo -> catalog
9
+ inputs -> N/A (managed by uv)
10
+ outputs -> editables
11
+
12
+ uv config:
13
+
14
+ # add from a remote catalog:
15
+ uv add recipe==1.0.3 --index blessed=https://path/to/blessed_catalog
16
+ # add from a path:
17
+ uv add recipe /path/to/blessed_catalog
18
+ # add from editables:
19
+ uv add --editable ./editable/recipe
20
+
21
+ ->
22
+ [tool.uv.sources]
23
+ torch = { index = "blessed" }
24
+
25
+ [[tool.uv.index]]
26
+ name = "blessed"
27
+ url = "https://path/to/blessed_catalog"
28
+ explicit = true #<-- see if this is better and if it can be set by uv cli
29
+
30
+ # add a requirement in a specific group
31
+ uv add builder_recipe --optional build
32
+
33
+ NOTE: environment marker on dependencies can use [extra] to select the catalog to install from:
34
+ https://docs.astral.sh/uv/concepts/projects/dependencies/#multiple-sources
35
+
36
+ """
37
+
38
+ from __future__ import annotations
39
+ from typing import Any
40
+
41
+ from pathlib import Path
42
+ import subprocess
43
+ import dataclasses
44
+ from importlib_metadata import Distribution
45
+ from ast import literal_eval
46
+ import os
47
+
48
+ import uv as external_uv
49
+ import pydantic
50
+
51
+ from tgzr.package_management.workspace import Workspace
52
+
53
+ from .recipe import RecipeTypeInfo
54
+ from .chef import Chef
55
+ from .plugin import cuisine_plugin_manager
56
+
57
+
58
+ class WorkbenchSettings(pydantic.BaseModel):
59
+ main_entity: str | None = (
60
+ None # optional main entity FIXME: should be auto installed
61
+ )
62
+
63
+
64
+ @dataclasses.dataclass
65
+ class RecipeDistInfo:
66
+ """
67
+ Recipe related information extracted from
68
+ a `importlib.metadata.Distribution()`.
69
+
70
+ These are created by Workbench.get_dist_info
71
+ """
72
+
73
+ dist: Distribution
74
+ is_recipe: bool
75
+ recipe_name: str
76
+ recipe_type: str | None
77
+ tags: set[str]
78
+ is_editable: bool
79
+ editable_path: Path | None
80
+ nice_panel_names: list[str]
81
+
82
+
83
+ class Workbench:
84
+ def __init__(self, path: Path | str | None) -> None:
85
+ if path is None:
86
+ path = "."
87
+ path = Path(path).resolve()
88
+ self._workspace = Workspace(path)
89
+
90
+ # self._name = self.path.name
91
+
92
+ # self._venv_name = ".venv"
93
+ # self._venv_path = self.path / self._venv_name
94
+
95
+ # exe_dir = "bin"
96
+ # self._exe_suffix = ""
97
+ # if platform.system() != "Linux":
98
+ # exe_dir = "Scripts"
99
+ # self._exe_suffix = ".exe"
100
+ # self._exe_path = self._venv_path / exe_dir
101
+
102
+ # self._external_packages_path = self.path / "external_packages"
103
+ # self._output_path = self.path / "outputs"
104
+ # self._uv_workspace_pyproject = self.path / "pyproject.toml"
105
+ self._editable_path = self.path / "editables"
106
+ self._build_path = self.path / "build"
107
+
108
+ venv_name = ".venv" # imposed by uv
109
+ self._venv_path = self.path / venv_name
110
+ self._chef = Chef(self._venv_path)
111
+ for plugin in cuisine_plugin_manager.get_plugins():
112
+ self._chef.register_recipe_types(*plugin.get_recipe_types())
113
+
114
+ @property
115
+ def path(self) -> Path:
116
+ """The path of the Workbench, including its name."""
117
+ return self._workspace.path
118
+
119
+ @property
120
+ def name(self) -> str:
121
+ """The name of the Workbench, deducted from its path."""
122
+ return self._workspace.name
123
+
124
+ # @property
125
+ # def venv_path(self) -> Path:
126
+ # """The path to the Workspace's venv."""
127
+ # return self._venv_path
128
+
129
+ def exists(self) -> bool:
130
+ return self._workspace.exists()
131
+
132
+ @property
133
+ def chef(self) -> Chef:
134
+ return self._chef
135
+
136
+ def get_recipe_types_info(self) -> dict[str, RecipeTypeInfo]:
137
+ recipe_types = self.chef.get_recipe_types()
138
+
139
+ ret = dict()
140
+ for RecipeType in recipe_types:
141
+ recipe_type_info = RecipeType.RECIPE_TYPE_INFO
142
+ ret[recipe_type_info.type_name] = recipe_type_info
143
+ return ret
144
+
145
+ def ensure_exists(
146
+ self,
147
+ force_build: bool = False,
148
+ description: str | None = None,
149
+ python_version: str | None = None,
150
+ ):
151
+ if self.exists():
152
+ if force_build:
153
+ # Not sure it's needed, and not sure how to do it ^_^', so:
154
+ raise ValueError("Workspace Rebuild not yet implemented!")
155
+ return
156
+
157
+ self._workspace.create(
158
+ description=description, python_version=python_version, vcs="none"
159
+ )
160
+ # Make all editable
161
+ self._workspace.add_member("editables/*")
162
+ # Seed the workspace with mandatory dependencies:
163
+ self._workspace.add_dependencies("", "uv", "hatch", "tgzr.cuisine")
164
+ self._workspace.ensure_index("pypi", "https://pypi.org/simple")
165
+ # self._workspace.set_source("uv", index_name="pypi")
166
+ # self._workspace.set_source("hatch", index_name="pypi")
167
+
168
+ # TODO: install from pypi:
169
+ self._workspace.set_source(
170
+ "tgzr.cuisine", path="/home/dee/DEV/_OPEN-TGZR_/tgzr.cuisine", editable=True
171
+ )
172
+
173
+ self._editable_path.mkdir(parents=False, exist_ok=True)
174
+ self._build_path.mkdir(parents=False, exist_ok=True)
175
+
176
+ def create_recipe(
177
+ self,
178
+ recipe_type_name: str,
179
+ recipe_name: str,
180
+ ):
181
+ """
182
+ Create an editable recipe on this workbench.
183
+ """
184
+ recipe_id = self.chef.create_recipe(
185
+ folder=self._editable_path,
186
+ recipe_name=recipe_name,
187
+ recipe_metadata=None,
188
+ version=None,
189
+ recipe_type_name=recipe_type_name,
190
+ )
191
+ # TODO: repace these 3 line with a subprocess `uv add {recipe_name}` ?
192
+ self._workspace.set_source(recipe_name, workspace=True)
193
+ self._workspace.add_dependencies("", recipe_name)
194
+ self.sync()
195
+ # self._workspace.sync(allow_upgrade=True, allow_custom_classifiers=True)
196
+ # self._install_editable(recipe_id)
197
+
198
+ def rebuid_pyproject(self, recipe_name: str):
199
+ """
200
+ Rebuild the pyproject.toml file, bump and install (editable)
201
+ Usefull when the content of the pyproject file need to be updated/conformed
202
+ """
203
+ self.chef.rebuild_pyproject(self._editable_path, recipe_name)
204
+ self.bump_recipe(recipe_name, "micro")
205
+
206
+ def rebuid_dinit(self, recipe_name: str):
207
+ """
208
+ Rebuild the __init__.py file, bump and install (editable)
209
+ Usefull when the content of the __init__ file need to be updated/conformed
210
+ after modifying what the base class puts in it.
211
+ """
212
+ self.chef.rebuild_dinit(self._editable_path, recipe_name)
213
+ self.bump_recipe(recipe_name, "micro")
214
+
215
+ def get_recipe_names(self) -> list[str]:
216
+ venv = self._workspace.venv()
217
+ dists = venv.get_packages()
218
+ recipe_dist_infos = [self.get_dist_info(dist) for dist in dists]
219
+ return [dist.recipe_name for dist in recipe_dist_infos if dist.is_recipe]
220
+
221
+ def get_editable_recipe_names(self) -> list[str]:
222
+ names = []
223
+ for path in self._editable_path.iterdir():
224
+ if (path / "pyproject.toml").exists:
225
+ names.append(path.name)
226
+ return sorted(names)
227
+
228
+ # def _install_editable(self, recipe_requirement: str):
229
+ # print("WS Install editable", recipe_requirement)
230
+
231
+ # find_links = []
232
+ # # TODO: implement management of uv's workspace index + use in them here
233
+
234
+ # self.chef.install_editable(
235
+ # self._editable_path,
236
+ # recipe_name,
237
+ # *find_links,
238
+ # )
239
+
240
+ def sync(self) -> None:
241
+ """
242
+ Install all editables an update all versions.
243
+ """
244
+ self._workspace.sync(allow_upgrade=True, allow_custom_classifiers=True)
245
+
246
+ def tag_recipe(self, recipe_name: str, *tags: str):
247
+ self.chef.add_tags(self._editable_path, recipe_name, set(tags))
248
+ self.sync()
249
+ # self._workspace.sync(allow_upgrade=True, allow_custom_classifiers=True)
250
+ # self._install_editable(recipe_name)
251
+
252
+ def add_inputs(self, recipe_name: str, group: str = "", *input_requirements: str):
253
+ """
254
+ Make this recipe dependent of given input_requirements.
255
+ Each input_requirement can be like:
256
+ - recipe-name
257
+ - recipe-name==1.2.3
258
+ - recipe-anme>=2.3.4
259
+ - recipe-name>=3.4.5<4.0
260
+ etc...
261
+ """
262
+ if not self.has_editable_recipe(recipe_name):
263
+ raise ValueError(f"Cannot add inputs to non-editable asset {recipe_name!r}")
264
+ self.chef.add_inputs(
265
+ self._editable_path, recipe_name, group, *input_requirements
266
+ )
267
+ self.sync()
268
+ # self._workspace.sync(allow_upgrade=True, allow_custom_classifiers=True)
269
+ # self._install_editable(recipe_name)
270
+
271
+ def bump_recipe(self, recipe_name: str, bump: str = "minor"):
272
+ """
273
+ Bumpt the part of the version specified with bump, like:"
274
+ major
275
+ minor
276
+ micro / patch / fix
277
+ a / alpha
278
+ b / alpha
279
+ c / rc / pre / preview
280
+ r / rev / post
281
+ dev
282
+
283
+ or a combination like:
284
+ minor,rc
285
+ patch,a
286
+ major,alpha,dev
287
+
288
+ Defaults is to bump minor."
289
+ """
290
+ self.chef.bump_recipe(self._editable_path, recipe_name, bump=bump)
291
+ self.sync()
292
+ # self._workspace.sync(allow_upgrade=True, allow_custom_classifiers=True)
293
+ # self._install_editable(recipe_name)
294
+
295
+ def build_recipe(self, recipe_name: str, bump: str = "minor"):
296
+ self.chef.bump_recipe(self._editable_path, recipe_name, bump=bump)
297
+ self.chef.build_recipe(self._editable_path, recipe_name, self._build_path)
298
+
299
+ def publish_recipe(self, recipe_name: str, index_name: str):
300
+ index = self._workspace.get_index(index_name)
301
+ if index is None:
302
+ raise ValueError(
303
+ f"Undefined index {index_name!r} in workbench {str(self.path)!r}."
304
+ )
305
+ self.chef.publish_recipe(
306
+ self._editable_path,
307
+ recipe_name,
308
+ self._build_path,
309
+ index.url,
310
+ )
311
+
312
+ def install_recipe(
313
+ self,
314
+ from_index_name: str,
315
+ requirement,
316
+ ):
317
+ self._workspace.set_source(requirement, index_name=from_index_name)
318
+ self.sync()
319
+ # self._workspace.sync(allow_upgrade=True, allow_custom_classifiers=True)
320
+
321
+ def has_editable_recipe(self, recipe_name: str):
322
+ return (self._editable_path / recipe_name).exists()
323
+
324
+ def turn_recipe_editable(self, recipe_name: str, force: bool = False):
325
+ """
326
+ Turn an installed recipe into an editable recipe.
327
+ """
328
+ if self.has_editable_recipe(recipe_name):
329
+ if not force:
330
+ raise ValueError(
331
+ f"The recipe {recipe_name!r} is already editable in the worksbench {self.path!r}."
332
+ )
333
+
334
+ command = f"import {recipe_name} as package; package.recipe._CONTROLER.create_editable(package.recipe, workbench_path='{self.path}', force={force})"
335
+ try:
336
+ self._workspace.run_python_command(command)
337
+ except:
338
+ print("Oopsy, runing python command returned non-zero !")
339
+ else:
340
+ self.chef.bump_recipe(self._editable_path, recipe_name, bump="micro")
341
+ self._workspace.set_source(recipe_name, workspace=True)
342
+ self.sync()
343
+ # self._workspace.sync(allow_upgrade=True, allow_custom_classifiers=True)
344
+
345
+ def get_dist_info(self, dist: Distribution) -> RecipeDistInfo:
346
+ is_editable = False
347
+ editable_path = None
348
+ if (
349
+ hasattr(dist, "origin")
350
+ and dist.origin is not None
351
+ and hasattr(dist.origin, "dir_info")
352
+ and dist.origin.dir_info.editable
353
+ ):
354
+ # TODO: verify if the editable path in in our output dir?
355
+ is_editable = True
356
+ url = dist.origin.url
357
+ editable_path = Path(url.split("file://", 1)[-1])
358
+
359
+ dist_info = RecipeDistInfo(
360
+ dist=dist,
361
+ is_recipe="tgzr.pipeline.asset_info_trick" in dist.entry_points.groups,
362
+ recipe_name=dist.name,
363
+ recipe_type=None,
364
+ tags=set(),
365
+ is_editable=is_editable,
366
+ editable_path=editable_path,
367
+ nice_panel_names=[],
368
+ )
369
+ if dist_info.is_recipe:
370
+ for ep in dist.entry_points.select(group="tgzr.pipeline.recipe_info_trick"):
371
+ if ep.name == "recipe_name":
372
+ dist_info.recipe_name = ep.value
373
+ elif ep.name == "recipe_type":
374
+ dist_info.recipe_type = ep.value
375
+ # TODO: read tags from keywords
376
+ # elif ep.name == "tags":
377
+ # try:
378
+ # dist_info.tags = literal_eval(ep.value)
379
+ # print(
380
+ # f"Error evaluating asset tags for {dist.name}: {ep.value}"
381
+ # )
382
+ # except:
383
+ # dist_info.tags = set()
384
+
385
+ for ep in dist.entry_points.select(group="tgzr.pipeline.asset.nice_panel"):
386
+ dist_info.nice_panel_names.append(ep.name)
387
+
388
+ return dist_info
389
+
390
+ def get_dist_infos(
391
+ self, editable_only: bool = False, include_non_recipe: bool = False
392
+ ) -> list[RecipeDistInfo]:
393
+ venv = self._workspace.venv()
394
+
395
+ def match(dist):
396
+ if editable_only and not dist.is_editable:
397
+ return False
398
+ if not include_non_recipe and not dist.is_recipe:
399
+ return False
400
+ return True
401
+
402
+ dists = venv.get_packages()
403
+ dist_infos = [self.get_dist_info(dist) for dist in dists]
404
+ return [di for di in dist_infos if match(di)]
405
+
406
+ def run(self, console_script_name: str):
407
+ extra_env = {"tgzr.pipeline.current_workspace.path": str(self.path)}
408
+ self._workspace.run(console_script_name, **extra_env)
409
+
410
+ # self._venv_path
411
+ # uv = self._venv_bin_path / "uv"
412
+ # python = self._venv_bin_path / "python"
413
+ # cmd = [
414
+ # str(uv),
415
+ # "run",
416
+ # "--python",
417
+ # str(python),
418
+ # "--directory",
419
+ # str(self._venv_bin_path),
420
+ # console_script_name,
421
+ # ]
422
+ # # print(cmd)
423
+ # env = os.environ.copy()
424
+ # env["tgzr.pipeline.current_workspace.path"] = str(self.path)
425
+ # # print(env)
426
+ # err_code = subprocess.call(cmd, env=env)
427
+ # if err_code:
428
+ # print(
429
+ # f"Oops, 'uv run {console_script_name}' returned error code: {err_code}."
430
+ # )
431
+
432
+ def run_recipe_method(
433
+ self, recipe_name: str, method_name: str, *args, **kwargs
434
+ ) -> Any:
435
+ """
436
+ Execute that Recipe's method in the current thread.
437
+
438
+ This is probably dangerous. Doing things like modifying the
439
+ asset in that method would be quite a bad ides I guess...
440
+ """
441
+ return self.chef.run_recipe_method(
442
+ self._editable_path, recipe_name, method_name, *args, **kwargs
443
+ )
444
+
445
+ def render_nice_panel(self, recipe_name: str, panel_name: str) -> Any:
446
+ """
447
+ Assets can implement GUI panels (see Asset.nice_panel_names())
448
+ When they do so, their DistInfo.nice_panel_names contains the
449
+ name of the panel they can render.
450
+ You can call this method under a nicegui.ui element context
451
+ to render the asset's panel.
452
+ """
453
+ return self.run_recipe_method(
454
+ recipe_name, method_name=panel_name, workspace=self
455
+ )
@@ -0,0 +1,34 @@
1
+ Metadata-Version: 2.4
2
+ Name: tgzr.cuisine
3
+ Version: 0.0.1
4
+ Summary: tgzr cuisine engine
5
+ Project-URL: Documentation, https://github.com/open-tgzr/tgzr.cuisine#readme
6
+ Project-URL: Issues, https://github.com/open-tgzr/tgzr.cuisine/issues
7
+ Project-URL: Source, https://github.com/open-tgzr/tgzr.cuisine
8
+ Author-email: Dee <dee.sometech@gmail.com>
9
+ License-Expression: GPL-3.0-or-later
10
+ License-File: LICENSE
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Programming Language :: Python
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: Implementation :: CPython
19
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
20
+ Requires-Python: >=3.9
21
+ Requires-Dist: click
22
+ Requires-Dist: eval-type-backport
23
+ Requires-Dist: hatch
24
+ Requires-Dist: importlib-metadata
25
+ Requires-Dist: pydantic
26
+ Requires-Dist: rich
27
+ Requires-Dist: tgzr-context
28
+ Requires-Dist: tgzr-package-management
29
+ Requires-Dist: toml
30
+ Requires-Dist: uv
31
+ Description-Content-Type: text/markdown
32
+
33
+ # tgzr.cuisine
34
+ Cusine let your perform your tasks by crafting and cooking recipes.
@@ -0,0 +1,30 @@
1
+ tgzr/cuisine/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ tgzr/cuisine/_version.py,sha256=qf6R-J7-UyuABBo8c0HgaquJ8bejVbf07HodXgwAwgQ,704
3
+ tgzr/cuisine/chef.py,sha256=6vifcXhq9UUmucfHssp_EHMgfI2W5-vdxZN7U1bxTpQ,11957
4
+ tgzr/cuisine/plugin.py,sha256=rF3lTwyFlWv9tEEUAr3os92THK4R5sk723qMPLMbXzM,634
5
+ tgzr/cuisine/recipe.py,sha256=C1mDsU_Kd1E29GmKRLNrnOaCArFBWJqq4z4JDj4D6rI,12817
6
+ tgzr/cuisine/workbench.py,sha256=XlQDHU4AaA190VYRYvA4CIXq55y242RjqxL-upZdi8I,15630
7
+ tgzr/cuisine/basics/__init__.py,sha256=EPN3Km7k6cfT0dx2dYBzaSkt2a0qF9V1ZFN21vmbcS8,829
8
+ tgzr/cuisine/basics/buildable.py,sha256=TYutAmB9e7T1AjhftuzUyL_FK2qOVvcAYvwYfkUQEeo,967
9
+ tgzr/cuisine/basics/builder.py,sha256=P1HhjDtjkL6wapRhKY-tUFy92yWV56X5GFm-yV5rXqU,2608
10
+ tgzr/cuisine/basics/computable.py,sha256=SjAqfkcV9R9wbQ1t0aQIdYroJKea7wIKQWGxxQcJxd4,917
11
+ tgzr/cuisine/basics/editable.py,sha256=J6k_1Qiblqxt5DUiRhdVFPt8DyGmD7WXSkeZY7uXiaM,718
12
+ tgzr/cuisine/basics/editor.py,sha256=IwcgNl_djrukQv-nWP7cwS4erde3R6f5MgPKiJRBdLw,854
13
+ tgzr/cuisine/basics/env.py,sha256=mSNqDK3RM6D6Em4Ba0Ok86z7aRvryLhRRyexhuEbGD4,6251
14
+ tgzr/cuisine/basics/product.py,sha256=bkDHSNVumNYg4q-5O-Lb9Am9h9qlbGwKWkR_YEgv0OY,989
15
+ tgzr/cuisine/basics/recipe_contexts.py,sha256=GEt9SkmnIRRaiFZ-CYZ_15zwUWr8wbCaRg2qc50r3FI,319
16
+ tgzr/cuisine/basics/recipe_with_params.py,sha256=CAhMnOPvhEN7lX8HzkUCsO13TW_g7wLnHpQxCiL4G3w,3727
17
+ tgzr/cuisine/basics/viewable.py,sha256=0ee8Tljwd_N4GbYiEwjAkVe1v38jATDKA2_q7e3xn-U,865
18
+ tgzr/cuisine/basics/viewer.py,sha256=jYQhRUpDCGqnil26wD47U27SdsrOpJ770HqHJZWpdj8,901
19
+ tgzr/cuisine/basics/workscene.py,sha256=YryFFIAaL9jd-5v5XYrZVKyknuLnne8Enn7egcwGREg,88
20
+ tgzr/cuisine/basics/files_recipes/__init__.py,sha256=lYi2lwJlgTktwDJ7z4-uZf49sBXNHFz-0SpFO9pm61A,480
21
+ tgzr/cuisine/basics/panels/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
+ tgzr/cuisine/basics/panels/params_panel.py,sha256=AWg5T62PiAViLeViKBtvTFf0ChtsH1jG3gBVS_Vmqrg,18118
23
+ tgzr/cuisine/cli/__init__.py,sha256=bD8gnm_IH-SaflfpL5YTKXhdUHxTNpcHB6UFMBaVr38,5810
24
+ tgzr/cuisine/cli/main.py,sha256=_dV9wqpoU5PMQeNKZiUGdt18xZUj4r2qkD7CYR9ii3Y,80
25
+ tgzr/cuisine/cli/utils.py,sha256=dafT-pQuV2Fnkq5od2jv31Ghu-ZtvoHzsCgo2zT8iGI,1034
26
+ tgzr_cuisine-0.0.1.dist-info/METADATA,sha256=8I-dhdZ358DaVUvSM0E43ZN5okZLhriDHr4BdY58mMw,1283
27
+ tgzr_cuisine-0.0.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
28
+ tgzr_cuisine-0.0.1.dist-info/entry_points.txt,sha256=WP7UXVs6sZVYsb3Xx0BluJ2WFLfusdnLiWX4BB5zhU4,203
29
+ tgzr_cuisine-0.0.1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
30
+ tgzr_cuisine-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.28.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,8 @@
1
+ [console_scripts]
2
+ cuisine = tgzr.cuisine.cli.main:main
3
+
4
+ [tgzr.cli.plugin]
5
+ cuisine_cli = tgzr.cuisine.tgzr_cli_plugin:install_cli
6
+
7
+ [tgzr.cuisine.plugin]
8
+ default_recipes = tgzr.cuisine.basics:BasicsPlugin