documentation and completion for plugins

This commit is contained in:
Federico Luzzi 2023-12-15 12:27:51 -03:00
parent 54a539c51a
commit e07f7ff130
6 changed files with 660 additions and 75 deletions

View File

@ -76,6 +76,7 @@ options:
conn pc@office conn pc@office
conn server conn server
``` ```
## Plugin Requirements for Connpy
### General Structure ### General Structure
- The plugin script must be a Python file. - The plugin script must be a Python file.
- Only the following top-level elements are allowed in the plugin script: - Only the following top-level elements are allowed in the plugin script:
@ -312,13 +313,15 @@ from .configfile import configfile
from .connapp import connapp from .connapp import connapp
from .api import * from .api import *
from .ai import ai from .ai import ai
from .plugins import Plugins
from ._version import __version__ from ._version import __version__
from pkg_resources import get_distribution from pkg_resources import get_distribution
__all__ = ["node", "nodes", "configfile", "connapp", "ai"] __all__ = ["node", "nodes", "configfile", "connapp", "ai", "Plugins"]
__author__ = "Federico Luzzi" __author__ = "Federico Luzzi"
__pdoc__ = { __pdoc__ = {
'core': False, 'core': False,
'completion': False, 'completion': False,
'api': False 'api': False,
'plugins': False
} }

View File

@ -1,2 +1,2 @@
__version__ = "3.8.0b2" __version__ = "3.8.0b3"

View File

@ -2,6 +2,7 @@ import sys
import os import os
import json import json
import glob import glob
import importlib.util
def _getallnodes(config): def _getallnodes(config):
#get all nodes on configfile #get all nodes on configfile
@ -47,15 +48,49 @@ def _getcwd(words, option, folderonly=False):
pathstrings = [s for s in pathstrings if os.path.isdir(s)] pathstrings = [s for s in pathstrings if os.path.isdir(s)]
return pathstrings return pathstrings
def _get_plugins(which, defaultdir):
enabled_files = []
disabled_files = []
all_files = []
all_plugins = {}
# Iterate over all files in the specified folder
for file in os.listdir(defaultdir + "/plugins"):
# Check if the file is a Python file
if file.endswith('.py'):
enabled_files.append(os.path.splitext(file)[0])
all_plugins[os.path.splitext(file)[0]] = os.path.join(defaultdir + "/plugins", file)
# Check if the file is a Python backup file
elif file.endswith('.py.bkp'):
disabled_files.append(os.path.splitext(os.path.splitext(file)[0])[0])
if which == "--disable":
return enabled_files
elif which == "--enable":
return disabled_files
elif which == "--del":
all_files.extend(enabled_files)
all_files.extend(disabled_files)
return all_files
elif which == "all":
return all_plugins
def main(): def main():
home = os.path.expanduser("~") home = os.path.expanduser("~")
defaultdir = home + '/.config/conn' defaultdir = home + '/.config/conn'
defaultfile = defaultdir + '/config.json' pathfile = defaultdir + '/.folder'
try:
with open(pathfile, "r") as f:
configdir = f.read().strip()
except:
configdir = defaultdir
defaultfile = configdir + '/config.json'
jsonconf = open(defaultfile) jsonconf = open(defaultfile)
config = json.load(jsonconf) config = json.load(jsonconf)
nodes = _getallnodes(config) nodes = _getallnodes(config)
folders = _getallfolders(config) folders = _getallfolders(config)
profiles = list(config["profiles"].keys()) profiles = list(config["profiles"].keys())
plugins = _get_plugins("all", defaultdir)
app = sys.argv[1] app = sys.argv[1]
if app in ["bash", "zsh"]: if app in ["bash", "zsh"]:
positions = [2,4] positions = [2,4]
@ -64,10 +99,21 @@ def main():
wordsnumber = int(sys.argv[positions[0]]) wordsnumber = int(sys.argv[positions[0]])
words = sys.argv[positions[1]:] words = sys.argv[positions[1]:]
if wordsnumber == 2: if wordsnumber == 2:
strings=["--add", "--del", "--rm", "--edit", "--mod", "--show", "mv", "move", "ls", "list", "cp", "copy", "profile", "run", "bulk", "config", "api", "ai", "export", "import", "--help"] strings=["--add", "--del", "--rm", "--edit", "--mod", "--show", "mv", "move", "ls", "list", "cp", "copy", "profile", "run", "bulk", "config", "api", "ai", "export", "import", "--help", "plugin"]
if plugins:
strings.extend(plugins.keys())
strings.extend(nodes) strings.extend(nodes)
strings.extend(folders) strings.extend(folders)
elif wordsnumber >=3 and words[0] in plugins.keys():
try:
spec = importlib.util.spec_from_file_location("module.name", plugins[words[0]])
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
plugin_completion = getattr(module, "_connpy_completion")
strings = plugin_completion(wordsnumber, words)
except:
exit()
elif wordsnumber >= 3 and words[0] == "ai": elif wordsnumber >= 3 and words[0] == "ai":
if wordsnumber == 3: if wordsnumber == 3:
strings = ["--help", "--org", "--model", "--api_key"] strings = ["--help", "--org", "--model", "--api_key"]
@ -91,6 +137,8 @@ def main():
strings.extend(folders) strings.extend(folders)
if words[0] in ["--rm", "--del", "-r", "--mod", "--edit", "-e", "--show", "-s", "mv", "move", "cp", "copy"]: if words[0] in ["--rm", "--del", "-r", "--mod", "--edit", "-e", "--show", "-s", "mv", "move", "cp", "copy"]:
strings.extend(nodes) strings.extend(nodes)
if words[0] == "plugin":
strings = ["--help", "--add", "--del", "--enable", "--disable"]
if words[0] in ["run", "import", "export"]: if words[0] in ["run", "import", "export"]:
strings = ["--help"] strings = ["--help"]
if words[0] == "export": if words[0] == "export":
@ -120,6 +168,11 @@ def main():
strings=["true", "false"] strings=["true", "false"]
if words[0] == "config" and words[1] in ["--configfolder"]: if words[0] == "config" and words[1] in ["--configfolder"]:
strings=_getcwd(words,words[0],True) strings=_getcwd(words,words[0],True)
if words[0] == "plugin" and words[1] in ["--del", "--enable", "--disable"]:
strings=_get_plugins(words[1], defaultdir)
elif wordsnumber == 5 and words[0] == "plugin" and words[1] == "--add":
strings=_getcwd(words, words[0])
else: else:
exit() exit()

View File

@ -160,7 +160,7 @@ class connapp:
#Add plugins #Add plugins
file_path = self.config.defaultdir + "/plugins" file_path = self.config.defaultdir + "/plugins"
self.plugins = Plugins() self.plugins = Plugins()
self.plugins.import_plugins_to_argparse(file_path, subparsers) self.plugins._import_plugins_to_argparse(file_path, subparsers)
#Generate helps #Generate helps
nodeparser.usage = self._help("usage", subparsers) nodeparser.usage = self._help("usage", subparsers)
nodeparser.epilog = self._help("end", subparsers) nodeparser.epilog = self._help("end", subparsers)

View File

@ -11,6 +11,36 @@ class Plugins:
self.plugin_parsers = {} self.plugin_parsers = {}
def verify_script(self, file_path): def verify_script(self, file_path):
"""
Verifies that a given Python script meets specific structural requirements.
This function checks a Python script for compliance with predefined structural
rules. It ensures that the script contains only allowed top-level elements
(functions, classes, imports, pass statements, and a specific if __name__ block)
and that it includes mandatory classes with specific attributes and methods.
### Arguments:
- file_path (str): The file path of the Python script to be verified.
### Returns:
- str: A message indicating the type of violation if the script doesn't meet
the requirements, or False if all requirements are met.
### Verifications:
- The presence of only allowed top-level elements.
- The existence of two specific classes: 'Parser' and 'Entrypoint'.
- 'Parser' class must only have an '__init__' method and must assign 'self.parser'
and 'self.description'.
- 'Entrypoint' class must have an '__init__' method accepting specific arguments.
If any of these checks fail, the function returns an error message indicating
the reason. If the script passes all checks, the function returns False,
indicating successful verification.
### Exceptions:
- SyntaxError: If the script contains a syntax error, it is caught and
returned as a part of the error message.
"""
with open(file_path, 'r') as file: with open(file_path, 'r') as file:
source_code = file.read() source_code = file.read()
@ -60,14 +90,14 @@ class Plugins:
else: else:
return "Classes Entrypoint and Parser are mandatory" return "Classes Entrypoint and Parser are mandatory"
def import_from_path(self, path): def _import_from_path(self, path):
spec = importlib.util.spec_from_file_location("module.name", path) spec = importlib.util.spec_from_file_location("module.name", path)
module = importlib.util.module_from_spec(spec) module = importlib.util.module_from_spec(spec)
sys.modules["module.name"] = module sys.modules["module.name"] = module
spec.loader.exec_module(module) spec.loader.exec_module(module)
return module return module
def import_plugins_to_argparse(self, directory, subparsers): def _import_plugins_to_argparse(self, directory, subparsers):
for filename in os.listdir(directory): for filename in os.listdir(directory):
commands = subparsers.choices.keys() commands = subparsers.choices.keys()
if filename.endswith(".py"): if filename.endswith(".py"):
@ -80,7 +110,7 @@ class Plugins:
if check_file: if check_file:
continue continue
else: else:
self.plugins[root_filename] = self.import_from_path(filepath) self.plugins[root_filename] = self._import_from_path(filepath)
self.plugin_parsers[root_filename] = self.plugins[root_filename].Parser() self.plugin_parsers[root_filename] = self.plugins[root_filename].Parser()
subparsers.add_parser(root_filename, parents=[self.plugin_parsers[root_filename].parser], add_help=False, description=self.plugin_parsers[root_filename].description) subparsers.add_parser(root_filename, parents=[self.plugin_parsers[root_filename].parser], add_help=False, description=self.plugin_parsers[root_filename].description)

View File

@ -36,7 +36,7 @@
</code></pre> </code></pre>
<h3 id="usage">Usage</h3> <h3 id="usage">Usage</h3>
<pre><code>usage: conn [-h] [--add | --del | --mod | --show | --debug] [node|folder] [--sftp] <pre><code>usage: conn [-h] [--add | --del | --mod | --show | --debug] [node|folder] [--sftp]
conn {profile,move,copy,list,bulk,export,import,run,config,api,ai} ... conn {profile,move,mv,copy,cp,list,ls,bulk,export,import,ai,run,api,plugin,config} ...
positional arguments: positional arguments:
node|folder node[@subfolder][@folder] node|folder node[@subfolder][@folder]
@ -54,17 +54,18 @@ Options:
-t, --sftp Connects using sftp instead of ssh -t, --sftp Connects using sftp instead of ssh
Commands: Commands:
profile Manage profiles profile Manage profiles
move (mv) Move node move(mv) Move node
copy (cp) Copy node copy(cp) Copy node
list (ls) List profiles, nodes or folders list(ls) List profiles, nodes or folders
bulk Add nodes in bulk bulk Add nodes in bulk
export Export connection folder to Yaml file export Export connection folder to Yaml file
import Import connection folder to config from Yaml file import Import connection folder to config from Yaml file
run Run scripts or commands on nodes ai Make request to an AI
config Manage app config run Run scripts or commands on nodes
api Start and stop connpy api api Start and stop connpy api
ai Make request to an AI plugin Manage plugins
config Manage app config
</code></pre> </code></pre>
<h3 id="manage-profiles">Manage profiles</h3> <h3 id="manage-profiles">Manage profiles</h3>
<pre><code>usage: conn profile [-h] (--add | --del | --mod | --show) profile <pre><code>usage: conn profile [-h] (--add | --del | --mod | --show) profile
@ -90,6 +91,51 @@ options:
conn pc@office conn pc@office
conn server conn server
</code></pre> </code></pre>
<h2 id="plugin-requirements-for-connpy">Plugin Requirements for Connpy</h2>
<h3 id="general-structure">General Structure</h3>
<ul>
<li>The plugin script must be a Python file.</li>
<li>Only the following top-level elements are allowed in the plugin script:</li>
<li>Class definitions</li>
<li>Function definitions</li>
<li>Import statements</li>
<li>The <code>if __name__ == "__main__":</code> block for standalone execution</li>
<li>Pass statements</li>
</ul>
<h3 id="specific-class-requirements">Specific Class Requirements</h3>
<ul>
<li>The plugin script must define at least two specific classes:</li>
<li><strong>Class <code>Parser</code></strong>:<ul>
<li>Must contain only one method: <code>__init__</code>.</li>
<li>The <code>__init__</code> method must initialize at least two attributes:</li>
<li><code>self.parser</code>: An instance of <code>argparse.ArgumentParser</code>.</li>
<li><code>self.description</code>: A string containing the description of the parser.</li>
</ul>
</li>
<li><strong>Class <code>Entrypoint</code></strong>:<ul>
<li>Must have an <code>__init__</code> method that accepts exactly three parameters besides <code>self</code>:</li>
<li><code>args</code>: Arguments passed to the plugin.</li>
<li>The parser instance (typically <code>self.parser</code> from the <code>Parser</code> class).</li>
<li>The Connapp instance to interact with the Connpy app.</li>
</ul>
</li>
</ul>
<h3 id="executable-block">Executable Block</h3>
<ul>
<li>The plugin script can include an executable block:</li>
<li><code>if __name__ == "__main__":</code></li>
<li>This block allows the plugin to be run as a standalone script for testing or independent use.</li>
</ul>
<h3 id="script-verification">Script Verification</h3>
<ul>
<li>The <code>verify_script</code> method in <code>plugins.py</code> is used to check the plugin script's compliance with these standards.</li>
<li>Non-compliant scripts will be rejected to ensure consistency and proper functionality within the plugin system.</li>
<li></li>
</ul>
<h3 id="example-script">Example Script</h3>
<p>For a practical example of how to write a compatible plugin script, please refer to the following example:</p>
<p><a href="https://github.com/fluzzi/awspy">Example Plugin Script</a></p>
<p>This script demonstrates the required structure and implementation details according to the plugin system's standards.</p>
<h2 id="http-api">http API</h2> <h2 id="http-api">http API</h2>
<p>With the Connpy API you can run commands on devices using http requests</p> <p>With the Connpy API you can run commands on devices using http requests</p>
<h3 id="1-list-nodes">1. List Nodes</h3> <h3 id="1-list-nodes">1. List Nodes</h3>
@ -271,7 +317,7 @@ Connpy is a connection manager that allows you to store nodes to connect them fa
### Usage ### Usage
``` ```
usage: conn [-h] [--add | --del | --mod | --show | --debug] [node|folder] [--sftp] usage: conn [-h] [--add | --del | --mod | --show | --debug] [node|folder] [--sftp]
conn {profile,move,copy,list,bulk,export,import,run,config,api,ai} ... conn {profile,move,mv,copy,cp,list,ls,bulk,export,import,ai,run,api,plugin,config} ...
positional arguments: positional arguments:
node|folder node[@subfolder][@folder] node|folder node[@subfolder][@folder]
@ -289,17 +335,18 @@ Options:
-t, --sftp Connects using sftp instead of ssh -t, --sftp Connects using sftp instead of ssh
Commands: Commands:
profile Manage profiles profile Manage profiles
move (mv) Move node move(mv) Move node
copy (cp) Copy node copy(cp) Copy node
list (ls) List profiles, nodes or folders list(ls) List profiles, nodes or folders
bulk Add nodes in bulk bulk Add nodes in bulk
export Export connection folder to Yaml file export Export connection folder to Yaml file
import Import connection folder to config from Yaml file import Import connection folder to config from Yaml file
run Run scripts or commands on nodes ai Make request to an AI
config Manage app config run Run scripts or commands on nodes
api Start and stop connpy api api Start and stop connpy api
ai Make request to an AI plugin Manage plugins
config Manage app config
``` ```
### Manage profiles ### Manage profiles
@ -329,6 +376,46 @@ options:
conn pc@office conn pc@office
conn server conn server
``` ```
## Plugin Requirements for Connpy
### General Structure
- The plugin script must be a Python file.
- Only the following top-level elements are allowed in the plugin script:
- Class definitions
- Function definitions
- Import statements
- The `if __name__ == &#34;__main__&#34;:` block for standalone execution
- Pass statements
### Specific Class Requirements
- The plugin script must define at least two specific classes:
1. **Class `Parser`**:
- Must contain only one method: `__init__`.
- The `__init__` method must initialize at least two attributes:
- `self.parser`: An instance of `argparse.ArgumentParser`.
- `self.description`: A string containing the description of the parser.
2. **Class `Entrypoint`**:
- Must have an `__init__` method that accepts exactly three parameters besides `self`:
- `args`: Arguments passed to the plugin.
- The parser instance (typically `self.parser` from the `Parser` class).
- The Connapp instance to interact with the Connpy app.
### Executable Block
- The plugin script can include an executable block:
- `if __name__ == &#34;__main__&#34;:`
- This block allows the plugin to be run as a standalone script for testing or independent use.
### Script Verification
- The `verify_script` method in `plugins.py` is used to check the plugin script&#39;s compliance with these standards.
- Non-compliant scripts will be rejected to ensure consistency and proper functionality within the plugin system.
-
### Example Script
For a practical example of how to write a compatible plugin script, please refer to the following example:
[Example Plugin Script](https://github.com/fluzzi/awspy)
This script demonstrates the required structure and implementation details according to the plugin system&#39;s standards.
## http API ## http API
With the Connpy API you can run commands on devices using http requests With the Connpy API you can run commands on devices using http requests
@ -526,15 +613,17 @@ from .configfile import configfile
from .connapp import connapp from .connapp import connapp
from .api import * from .api import *
from .ai import ai from .ai import ai
from .plugins import Plugins
from ._version import __version__ from ._version import __version__
from pkg_resources import get_distribution from pkg_resources import get_distribution
__all__ = [&#34;node&#34;, &#34;nodes&#34;, &#34;configfile&#34;, &#34;connapp&#34;, &#34;ai&#34;] __all__ = [&#34;node&#34;, &#34;nodes&#34;, &#34;configfile&#34;, &#34;connapp&#34;, &#34;ai&#34;, &#34;Plugins&#34;]
__author__ = &#34;Federico Luzzi&#34; __author__ = &#34;Federico Luzzi&#34;
__pdoc__ = { __pdoc__ = {
&#39;core&#39;: False, &#39;core&#39;: False,
&#39;completion&#39;: False, &#39;completion&#39;: False,
&#39;api&#39;: False &#39;api&#39;: False,
&#39;plugins&#39;: False
}</code></pre> }</code></pre>
</details> </details>
</section> </section>
@ -547,6 +636,243 @@ __pdoc__ = {
<section> <section>
<h2 class="section-title" id="header-classes">Classes</h2> <h2 class="section-title" id="header-classes">Classes</h2>
<dl> <dl>
<dt id="connpy.Plugins"><code class="flex name class">
<span>class <span class="ident">Plugins</span></span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class Plugins:
def __init__(self):
self.plugins = {}
self.plugin_parsers = {}
def verify_script(self, file_path):
&#34;&#34;&#34;
Verifies that a given Python script meets specific structural requirements.
This function checks a Python script for compliance with predefined structural
rules. It ensures that the script contains only allowed top-level elements
(functions, classes, imports, pass statements, and a specific if __name__ block)
and that it includes mandatory classes with specific attributes and methods.
### Arguments:
- file_path (str): The file path of the Python script to be verified.
### Returns:
- str: A message indicating the type of violation if the script doesn&#39;t meet
the requirements, or False if all requirements are met.
### Verifications:
- The presence of only allowed top-level elements.
- The existence of two specific classes: &#39;Parser&#39; and &#39;Entrypoint&#39;.
- &#39;Parser&#39; class must only have an &#39;__init__&#39; method and must assign &#39;self.parser&#39;
and &#39;self.description&#39;.
- &#39;Entrypoint&#39; class must have an &#39;__init__&#39; method accepting specific arguments.
If any of these checks fail, the function returns an error message indicating
the reason. If the script passes all checks, the function returns False,
indicating successful verification.
### Exceptions:
- SyntaxError: If the script contains a syntax error, it is caught and
returned as a part of the error message.
&#34;&#34;&#34;
with open(file_path, &#39;r&#39;) as file:
source_code = file.read()
try:
tree = ast.parse(source_code)
except SyntaxError as e:
return f&#34;Syntax error in file: {e}&#34;
required_classes = {&#39;Parser&#39;, &#39;Entrypoint&#39;}
found_classes = set()
for node in tree.body:
# Allow only function definitions, class definitions, and pass statements at top-level
if isinstance(node, ast.If):
# Check for the &#39;if __name__ == &#34;__main__&#34;:&#39; block
if not (isinstance(node.test, ast.Compare) and
isinstance(node.test.left, ast.Name) and
node.test.left.id == &#39;__name__&#39; and
isinstance(node.test.comparators[0], ast.Str) and
node.test.comparators[0].s == &#39;__main__&#39;):
return &#34;Only __name__ == __main__ If is allowed&#34;
elif not isinstance(node, (ast.FunctionDef, ast.ClassDef, ast.Import, ast.ImportFrom, ast.Pass)):
return f&#34;Plugin can only have pass, functions, classes and imports. {node} is not allowed&#34; # Reject any other AST types
if isinstance(node, ast.ClassDef) and node.name in required_classes:
found_classes.add(node.name)
if node.name == &#39;Parser&#39;:
# Ensure Parser class has only the __init__ method and assigns self.parser
if not all(isinstance(method, ast.FunctionDef) and method.name == &#39;__init__&#39; for method in node.body):
return &#34;Parser class should only have __init__ method&#34;
# Check if &#39;self.parser&#39; and &#39;self.description&#39; are assigned in __init__ method
init_method = node.body[0]
assigned_attrs = [target.attr for expr in init_method.body if isinstance(expr, ast.Assign) for target in expr.targets if isinstance(target, ast.Attribute) and isinstance(target.value, ast.Name) and target.value.id == &#39;self&#39;]
if &#39;parser&#39; not in assigned_attrs or &#39;description&#39; not in assigned_attrs:
return &#34;Parser class should set self.parser and self.description&#34; # &#39;self.parser&#39; or &#39;self.description&#39; not assigned in __init__
elif node.name == &#39;Entrypoint&#39;:
init_method = next((item for item in node.body if isinstance(item, ast.FunctionDef) and item.name == &#39;__init__&#39;), None)
if not init_method or len(init_method.args.args) != 4: # self, args, parser, conapp
return &#34;Entrypoint class should accept only arguments: args, parser and connapp&#34; # &#39;Entrypoint&#39; __init__ does not have correct signature
if required_classes == found_classes:
return False
else:
return &#34;Classes Entrypoint and Parser are mandatory&#34;
def _import_from_path(self, path):
spec = importlib.util.spec_from_file_location(&#34;module.name&#34;, path)
module = importlib.util.module_from_spec(spec)
sys.modules[&#34;module.name&#34;] = module
spec.loader.exec_module(module)
return module
def _import_plugins_to_argparse(self, directory, subparsers):
for filename in os.listdir(directory):
commands = subparsers.choices.keys()
if filename.endswith(&#34;.py&#34;):
root_filename = os.path.splitext(filename)[0]
if root_filename in commands:
continue
# Construct the full path
filepath = os.path.join(directory, filename)
check_file = self.verify_script(filepath)
if check_file:
continue
else:
self.plugins[root_filename] = self._import_from_path(filepath)
self.plugin_parsers[root_filename] = self.plugins[root_filename].Parser()
subparsers.add_parser(root_filename, parents=[self.plugin_parsers[root_filename].parser], add_help=False, description=self.plugin_parsers[root_filename].description)</code></pre>
</details>
<h3>Methods</h3>
<dl>
<dt id="connpy.Plugins.verify_script"><code class="name flex">
<span>def <span class="ident">verify_script</span></span>(<span>self, file_path)</span>
</code></dt>
<dd>
<div class="desc"><p>Verifies that a given Python script meets specific structural requirements.</p>
<p>This function checks a Python script for compliance with predefined structural
rules. It ensures that the script contains only allowed top-level elements
(functions, classes, imports, pass statements, and a specific if <strong>name</strong> block)
and that it includes mandatory classes with specific attributes and methods.</p>
<h3 id="arguments">Arguments:</h3>
<pre><code>- file_path (str): The file path of the Python script to be verified.
</code></pre>
<h3 id="returns">Returns:</h3>
<pre><code>- str: A message indicating the type of violation if the script doesn't meet
the requirements, or False if all requirements are met.
</code></pre>
<h3 id="verifications">Verifications:</h3>
<pre><code>- The presence of only allowed top-level elements.
- The existence of two specific classes: 'Parser' and 'Entrypoint'.
- 'Parser' class must only have an '__init__' method and must assign 'self.parser'
and 'self.description'.
- 'Entrypoint' class must have an '__init__' method accepting specific arguments.
</code></pre>
<p>If any of these checks fail, the function returns an error message indicating
the reason. If the script passes all checks, the function returns False,
indicating successful verification.</p>
<h3 id="exceptions">Exceptions:</h3>
<pre><code> - SyntaxError: If the script contains a syntax error, it is caught and
returned as a part of the error message.
</code></pre></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def verify_script(self, file_path):
&#34;&#34;&#34;
Verifies that a given Python script meets specific structural requirements.
This function checks a Python script for compliance with predefined structural
rules. It ensures that the script contains only allowed top-level elements
(functions, classes, imports, pass statements, and a specific if __name__ block)
and that it includes mandatory classes with specific attributes and methods.
### Arguments:
- file_path (str): The file path of the Python script to be verified.
### Returns:
- str: A message indicating the type of violation if the script doesn&#39;t meet
the requirements, or False if all requirements are met.
### Verifications:
- The presence of only allowed top-level elements.
- The existence of two specific classes: &#39;Parser&#39; and &#39;Entrypoint&#39;.
- &#39;Parser&#39; class must only have an &#39;__init__&#39; method and must assign &#39;self.parser&#39;
and &#39;self.description&#39;.
- &#39;Entrypoint&#39; class must have an &#39;__init__&#39; method accepting specific arguments.
If any of these checks fail, the function returns an error message indicating
the reason. If the script passes all checks, the function returns False,
indicating successful verification.
### Exceptions:
- SyntaxError: If the script contains a syntax error, it is caught and
returned as a part of the error message.
&#34;&#34;&#34;
with open(file_path, &#39;r&#39;) as file:
source_code = file.read()
try:
tree = ast.parse(source_code)
except SyntaxError as e:
return f&#34;Syntax error in file: {e}&#34;
required_classes = {&#39;Parser&#39;, &#39;Entrypoint&#39;}
found_classes = set()
for node in tree.body:
# Allow only function definitions, class definitions, and pass statements at top-level
if isinstance(node, ast.If):
# Check for the &#39;if __name__ == &#34;__main__&#34;:&#39; block
if not (isinstance(node.test, ast.Compare) and
isinstance(node.test.left, ast.Name) and
node.test.left.id == &#39;__name__&#39; and
isinstance(node.test.comparators[0], ast.Str) and
node.test.comparators[0].s == &#39;__main__&#39;):
return &#34;Only __name__ == __main__ If is allowed&#34;
elif not isinstance(node, (ast.FunctionDef, ast.ClassDef, ast.Import, ast.ImportFrom, ast.Pass)):
return f&#34;Plugin can only have pass, functions, classes and imports. {node} is not allowed&#34; # Reject any other AST types
if isinstance(node, ast.ClassDef) and node.name in required_classes:
found_classes.add(node.name)
if node.name == &#39;Parser&#39;:
# Ensure Parser class has only the __init__ method and assigns self.parser
if not all(isinstance(method, ast.FunctionDef) and method.name == &#39;__init__&#39; for method in node.body):
return &#34;Parser class should only have __init__ method&#34;
# Check if &#39;self.parser&#39; and &#39;self.description&#39; are assigned in __init__ method
init_method = node.body[0]
assigned_attrs = [target.attr for expr in init_method.body if isinstance(expr, ast.Assign) for target in expr.targets if isinstance(target, ast.Attribute) and isinstance(target.value, ast.Name) and target.value.id == &#39;self&#39;]
if &#39;parser&#39; not in assigned_attrs or &#39;description&#39; not in assigned_attrs:
return &#34;Parser class should set self.parser and self.description&#34; # &#39;self.parser&#39; or &#39;self.description&#39; not assigned in __init__
elif node.name == &#39;Entrypoint&#39;:
init_method = next((item for item in node.body if isinstance(item, ast.FunctionDef) and item.name == &#39;__init__&#39;), None)
if not init_method or len(init_method.args.args) != 4: # self, args, parser, conapp
return &#34;Entrypoint class should accept only arguments: args, parser and connapp&#34; # &#39;Entrypoint&#39; __init__ does not have correct signature
if required_classes == found_classes:
return False
else:
return &#34;Classes Entrypoint and Parser are mandatory&#34;</code></pre>
</details>
</dd>
</dl>
</dd>
<dt id="connpy.ai"><code class="flex name class"> <dt id="connpy.ai"><code class="flex name class">
<span>class <span class="ident">ai</span></span> <span>class <span class="ident">ai</span></span>
<span>(</span><span>config, org=None, api_key=None, model=None, temp=0.7)</span> <span>(</span><span>config, org=None, api_key=None, model=None, temp=0.7)</span>
@ -1347,7 +1673,9 @@ Categorize the user&#39;s request based on the operation they want to perform on
&#39;&#39;&#39; &#39;&#39;&#39;
home = os.path.expanduser(&#34;~&#34;) home = os.path.expanduser(&#34;~&#34;)
defaultdir = home + &#39;/.config/conn&#39; defaultdir = home + &#39;/.config/conn&#39;
self.defaultdir = defaultdir
Path(defaultdir).mkdir(parents=True, exist_ok=True) Path(defaultdir).mkdir(parents=True, exist_ok=True)
Path(f&#34;{defaultdir}/plugins&#34;).mkdir(parents=True, exist_ok=True)
pathfile = defaultdir + &#39;/.folder&#39; pathfile = defaultdir + &#39;/.folder&#39;
try: try:
with open(pathfile, &#34;r&#34;) as f: with open(pathfile, &#34;r&#34;) as f:
@ -1861,9 +2189,9 @@ Categorize the user&#39;s request based on the operation they want to perform on
&#39;&#39;&#39; &#39;&#39;&#39;
#DEFAULTPARSER #DEFAULTPARSER
defaultparser = argparse.ArgumentParser(prog = &#34;conn&#34;, description = &#34;SSH and Telnet connection manager&#34;, formatter_class=argparse.RawTextHelpFormatter) defaultparser = argparse.ArgumentParser(prog = &#34;conn&#34;, description = &#34;SSH and Telnet connection manager&#34;, formatter_class=argparse.RawTextHelpFormatter)
subparsers = defaultparser.add_subparsers(title=&#34;Commands&#34;) subparsers = defaultparser.add_subparsers(title=&#34;Commands&#34;, dest=&#34;subcommand&#34;)
#NODEPARSER #NODEPARSER
nodeparser = subparsers.add_parser(&#34;node&#34;,usage=self._help(&#34;usage&#34;), help=self._help(&#34;node&#34;),epilog=self._help(&#34;end&#34;), formatter_class=argparse.RawTextHelpFormatter) nodeparser = subparsers.add_parser(&#34;node&#34;, formatter_class=argparse.RawTextHelpFormatter)
nodecrud = nodeparser.add_mutually_exclusive_group() nodecrud = nodeparser.add_mutually_exclusive_group()
nodeparser.add_argument(&#34;node&#34;, metavar=&#34;node|folder&#34;, nargs=&#39;?&#39;, default=None, action=self._store_type, help=self._help(&#34;node&#34;)) nodeparser.add_argument(&#34;node&#34;, metavar=&#34;node|folder&#34;, nargs=&#39;?&#39;, default=None, action=self._store_type, help=self._help(&#34;node&#34;))
nodecrud.add_argument(&#34;-v&#34;,&#34;--version&#34;, dest=&#34;action&#34;, action=&#34;store_const&#34;, help=&#34;Show version&#34;, const=&#34;version&#34;, default=&#34;connect&#34;) nodecrud.add_argument(&#34;-v&#34;,&#34;--version&#34;, dest=&#34;action&#34;, action=&#34;store_const&#34;, help=&#34;Show version&#34;, const=&#34;version&#34;, default=&#34;connect&#34;)
@ -1875,7 +2203,7 @@ Categorize the user&#39;s request based on the operation they want to perform on
nodeparser.add_argument(&#34;-t&#34;,&#34;--sftp&#34;, dest=&#34;sftp&#34;, action=&#34;store_true&#34;, help=&#34;Connects using sftp instead of ssh&#34;) nodeparser.add_argument(&#34;-t&#34;,&#34;--sftp&#34;, dest=&#34;sftp&#34;, action=&#34;store_true&#34;, help=&#34;Connects using sftp instead of ssh&#34;)
nodeparser.set_defaults(func=self._func_node) nodeparser.set_defaults(func=self._func_node)
#PROFILEPARSER #PROFILEPARSER
profileparser = subparsers.add_parser(&#34;profile&#34;, help=&#34;Manage profiles&#34;) profileparser = subparsers.add_parser(&#34;profile&#34;, description=&#34;Manage profiles&#34;)
profileparser.add_argument(&#34;profile&#34;, nargs=1, action=self._store_type, type=self._type_profile, help=&#34;Name of profile to manage&#34;) profileparser.add_argument(&#34;profile&#34;, nargs=1, action=self._store_type, type=self._type_profile, help=&#34;Name of profile to manage&#34;)
profilecrud = profileparser.add_mutually_exclusive_group(required=True) profilecrud = profileparser.add_mutually_exclusive_group(required=True)
profilecrud.add_argument(&#34;-a&#34;, &#34;--add&#34;, dest=&#34;action&#34;, action=&#34;store_const&#34;, help=&#34;Add new profile&#34;, const=&#34;add&#34;) profilecrud.add_argument(&#34;-a&#34;, &#34;--add&#34;, dest=&#34;action&#34;, action=&#34;store_const&#34;, help=&#34;Add new profile&#34;, const=&#34;add&#34;)
@ -1884,53 +2212,62 @@ Categorize the user&#39;s request based on the operation they want to perform on
profilecrud.add_argument(&#34;-s&#34;, &#34;--show&#34;, dest=&#34;action&#34;, action=&#34;store_const&#34;, help=&#34;Show profile&#34;, const=&#34;show&#34;) profilecrud.add_argument(&#34;-s&#34;, &#34;--show&#34;, dest=&#34;action&#34;, action=&#34;store_const&#34;, help=&#34;Show profile&#34;, const=&#34;show&#34;)
profileparser.set_defaults(func=self._func_profile) profileparser.set_defaults(func=self._func_profile)
#MOVEPARSER #MOVEPARSER
moveparser = subparsers.add_parser(&#34;move&#34;, aliases=[&#34;mv&#34;], help=&#34;Move node&#34;) moveparser = subparsers.add_parser(&#34;move&#34;, aliases=[&#34;mv&#34;], description=&#34;Move node&#34;)
moveparser.add_argument(&#34;move&#34;, nargs=2, action=self._store_type, help=&#34;Move node[@subfolder][@folder] dest_node[@subfolder][@folder]&#34;, default=&#34;move&#34;, type=self._type_node) moveparser.add_argument(&#34;move&#34;, nargs=2, action=self._store_type, help=&#34;Move node[@subfolder][@folder] dest_node[@subfolder][@folder]&#34;, default=&#34;move&#34;, type=self._type_node)
moveparser.set_defaults(func=self._func_others) moveparser.set_defaults(func=self._func_others)
#COPYPARSER #COPYPARSER
copyparser = subparsers.add_parser(&#34;copy&#34;, aliases=[&#34;cp&#34;], help=&#34;Copy node&#34;) copyparser = subparsers.add_parser(&#34;copy&#34;, aliases=[&#34;cp&#34;], description=&#34;Copy node&#34;)
copyparser.add_argument(&#34;cp&#34;, nargs=2, action=self._store_type, help=&#34;Copy node[@subfolder][@folder] new_node[@subfolder][@folder]&#34;, default=&#34;cp&#34;, type=self._type_node) copyparser.add_argument(&#34;cp&#34;, nargs=2, action=self._store_type, help=&#34;Copy node[@subfolder][@folder] new_node[@subfolder][@folder]&#34;, default=&#34;cp&#34;, type=self._type_node)
copyparser.set_defaults(func=self._func_others) copyparser.set_defaults(func=self._func_others)
#LISTPARSER #LISTPARSER
lsparser = subparsers.add_parser(&#34;list&#34;, aliases=[&#34;ls&#34;], help=&#34;List profiles, nodes or folders&#34;) lsparser = subparsers.add_parser(&#34;list&#34;, aliases=[&#34;ls&#34;], description=&#34;List profiles, nodes or folders&#34;)
lsparser.add_argument(&#34;ls&#34;, action=self._store_type, choices=[&#34;profiles&#34;,&#34;nodes&#34;,&#34;folders&#34;], help=&#34;List profiles, nodes or folders&#34;, default=False) lsparser.add_argument(&#34;ls&#34;, action=self._store_type, choices=[&#34;profiles&#34;,&#34;nodes&#34;,&#34;folders&#34;], help=&#34;List profiles, nodes or folders&#34;, default=False)
lsparser.add_argument(&#34;--filter&#34;, nargs=1, help=&#34;Filter results&#34;) lsparser.add_argument(&#34;--filter&#34;, nargs=1, help=&#34;Filter results&#34;)
lsparser.add_argument(&#34;--format&#34;, nargs=1, help=&#34;Format of the output of nodes using {name}, {NAME}, {location}, {LOCATION}, {host} and {HOST}&#34;) lsparser.add_argument(&#34;--format&#34;, nargs=1, help=&#34;Format of the output of nodes using {name}, {NAME}, {location}, {LOCATION}, {host} and {HOST}&#34;)
lsparser.set_defaults(func=self._func_others) lsparser.set_defaults(func=self._func_others)
#BULKPARSER #BULKPARSER
bulkparser = subparsers.add_parser(&#34;bulk&#34;, help=&#34;Add nodes in bulk&#34;) bulkparser = subparsers.add_parser(&#34;bulk&#34;, description=&#34;Add nodes in bulk&#34;)
bulkparser.add_argument(&#34;bulk&#34;, const=&#34;bulk&#34;, nargs=0, action=self._store_type, help=&#34;Add nodes in bulk&#34;) bulkparser.add_argument(&#34;bulk&#34;, const=&#34;bulk&#34;, nargs=0, action=self._store_type, help=&#34;Add nodes in bulk&#34;)
bulkparser.set_defaults(func=self._func_others) bulkparser.set_defaults(func=self._func_others)
# EXPORTPARSER # EXPORTPARSER
exportparser = subparsers.add_parser(&#34;export&#34;, help=&#34;Export connection folder to Yaml file&#34;) exportparser = subparsers.add_parser(&#34;export&#34;, description=&#34;Export connection folder to Yaml file&#34;)
exportparser.add_argument(&#34;export&#34;, nargs=&#34;+&#34;, action=self._store_type, help=&#34;Export /path/to/file.yml [@subfolder1][@folder1] [@subfolderN][@folderN]&#34;) exportparser.add_argument(&#34;export&#34;, nargs=&#34;+&#34;, action=self._store_type, help=&#34;Export /path/to/file.yml [@subfolder1][@folder1] [@subfolderN][@folderN]&#34;)
exportparser.set_defaults(func=self._func_export) exportparser.set_defaults(func=self._func_export)
# IMPORTPARSER # IMPORTPARSER
importparser = subparsers.add_parser(&#34;import&#34;, help=&#34;Import connection folder to config from Yaml file&#34;) importparser = subparsers.add_parser(&#34;import&#34;, description=&#34;Import connection folder to config from Yaml file&#34;)
importparser.add_argument(&#34;file&#34;, nargs=1, action=self._store_type, help=&#34;Import /path/to/file.yml&#34;) importparser.add_argument(&#34;file&#34;, nargs=1, action=self._store_type, help=&#34;Import /path/to/file.yml&#34;)
importparser.set_defaults(func=self._func_import) importparser.set_defaults(func=self._func_import)
# AIPARSER # AIPARSER
aiparser = subparsers.add_parser(&#34;ai&#34;, help=&#34;Make request to an AI&#34;) aiparser = subparsers.add_parser(&#34;ai&#34;, description=&#34;Make request to an AI&#34;)
aiparser.add_argument(&#34;ask&#34;, nargs=&#39;*&#39;, help=&#34;Ask connpy AI something&#34;) aiparser.add_argument(&#34;ask&#34;, nargs=&#39;*&#39;, help=&#34;Ask connpy AI something&#34;)
aiparser.add_argument(&#34;--model&#34;, nargs=1, help=&#34;Set the OPENAI model id&#34;) aiparser.add_argument(&#34;--model&#34;, nargs=1, help=&#34;Set the OPENAI model id&#34;)
aiparser.add_argument(&#34;--org&#34;, nargs=1, help=&#34;Set the OPENAI organization id&#34;) aiparser.add_argument(&#34;--org&#34;, nargs=1, help=&#34;Set the OPENAI organization id&#34;)
aiparser.add_argument(&#34;--api_key&#34;, nargs=1, help=&#34;Set the OPENAI API key&#34;) aiparser.add_argument(&#34;--api_key&#34;, nargs=1, help=&#34;Set the OPENAI API key&#34;)
aiparser.set_defaults(func=self._func_ai) aiparser.set_defaults(func=self._func_ai)
#RUNPARSER #RUNPARSER
runparser = subparsers.add_parser(&#34;run&#34;, help=&#34;Run scripts or commands on nodes&#34;, formatter_class=argparse.RawTextHelpFormatter) runparser = subparsers.add_parser(&#34;run&#34;, description=&#34;Run scripts or commands on nodes&#34;, formatter_class=argparse.RawTextHelpFormatter)
runparser.add_argument(&#34;run&#34;, nargs=&#39;+&#39;, action=self._store_type, help=self._help(&#34;run&#34;), default=&#34;run&#34;) runparser.add_argument(&#34;run&#34;, nargs=&#39;+&#39;, action=self._store_type, help=self._help(&#34;run&#34;), default=&#34;run&#34;)
runparser.add_argument(&#34;-g&#34;,&#34;--generate&#34;, dest=&#34;action&#34;, action=&#34;store_const&#34;, help=&#34;Generate yaml file template&#34;, const=&#34;generate&#34;, default=&#34;run&#34;) runparser.add_argument(&#34;-g&#34;,&#34;--generate&#34;, dest=&#34;action&#34;, action=&#34;store_const&#34;, help=&#34;Generate yaml file template&#34;, const=&#34;generate&#34;, default=&#34;run&#34;)
runparser.set_defaults(func=self._func_run) runparser.set_defaults(func=self._func_run)
#APIPARSER #APIPARSER
apiparser = subparsers.add_parser(&#34;api&#34;, help=&#34;Start and stop connpy api&#34;) apiparser = subparsers.add_parser(&#34;api&#34;, description=&#34;Start and stop connpy api&#34;)
apicrud = apiparser.add_mutually_exclusive_group(required=True) apicrud = apiparser.add_mutually_exclusive_group(required=True)
apicrud.add_argument(&#34;-s&#34;,&#34;--start&#34;, dest=&#34;start&#34;, nargs=&#34;?&#34;, action=self._store_type, help=&#34;Start conppy api&#34;, type=int, default=8048, metavar=&#34;PORT&#34;) apicrud.add_argument(&#34;-s&#34;,&#34;--start&#34;, dest=&#34;start&#34;, nargs=&#34;?&#34;, action=self._store_type, help=&#34;Start conppy api&#34;, type=int, default=8048, metavar=&#34;PORT&#34;)
apicrud.add_argument(&#34;-r&#34;,&#34;--restart&#34;, dest=&#34;restart&#34;, nargs=0, action=self._store_type, help=&#34;Restart conppy api&#34;) apicrud.add_argument(&#34;-r&#34;,&#34;--restart&#34;, dest=&#34;restart&#34;, nargs=0, action=self._store_type, help=&#34;Restart conppy api&#34;)
apicrud.add_argument(&#34;-x&#34;,&#34;--stop&#34;, dest=&#34;stop&#34;, nargs=0, action=self._store_type, help=&#34;Stop conppy api&#34;) apicrud.add_argument(&#34;-x&#34;,&#34;--stop&#34;, dest=&#34;stop&#34;, nargs=0, action=self._store_type, help=&#34;Stop conppy api&#34;)
apicrud.add_argument(&#34;-d&#34;, &#34;--debug&#34;, dest=&#34;debug&#34;, nargs=&#34;?&#34;, action=self._store_type, help=&#34;Run connpy server on debug mode&#34;, type=int, default=8048, metavar=&#34;PORT&#34;) apicrud.add_argument(&#34;-d&#34;, &#34;--debug&#34;, dest=&#34;debug&#34;, nargs=&#34;?&#34;, action=self._store_type, help=&#34;Run connpy server on debug mode&#34;, type=int, default=8048, metavar=&#34;PORT&#34;)
apiparser.set_defaults(func=self._func_api) apiparser.set_defaults(func=self._func_api)
#PLUGINSPARSER
pluginparser = subparsers.add_parser(&#34;plugin&#34;, description=&#34;Manage plugins&#34;)
plugincrud = pluginparser.add_mutually_exclusive_group(required=True)
plugincrud.add_argument(&#34;--add&#34;, metavar=(&#34;PLUGIN&#34;, &#34;FILE&#34;), nargs=2, help=&#34;Add new plugin&#34;)
plugincrud.add_argument(&#34;--del&#34;, dest=&#34;delete&#34;, metavar=&#34;PLUGIN&#34;, nargs=1, help=&#34;Delete plugin&#34;)
plugincrud.add_argument(&#34;--enable&#34;, metavar=&#34;PLUGIN&#34;, nargs=1, help=&#34;Enable plugin&#34;)
plugincrud.add_argument(&#34;--disable&#34;, metavar=&#34;PLUGIN&#34;, nargs=1, help=&#34;Disable plugin&#34;)
plugincrud.add_argument(&#34;--list&#34;, dest=&#34;list&#34;, action=&#34;store_true&#34;, help=&#34;Disable plugin&#34;)
pluginparser.set_defaults(func=self._func_plugin)
#CONFIGPARSER #CONFIGPARSER
configparser = subparsers.add_parser(&#34;config&#34;, help=&#34;Manage app config&#34;) configparser = subparsers.add_parser(&#34;config&#34;, description=&#34;Manage app config&#34;)
configcrud = configparser.add_mutually_exclusive_group(required=True) configcrud = configparser.add_mutually_exclusive_group(required=True)
configcrud.add_argument(&#34;--allow-uppercase&#34;, dest=&#34;case&#34;, nargs=1, action=self._store_type, help=&#34;Allow case sensitive names&#34;, choices=[&#34;true&#34;,&#34;false&#34;]) configcrud.add_argument(&#34;--allow-uppercase&#34;, dest=&#34;case&#34;, nargs=1, action=self._store_type, help=&#34;Allow case sensitive names&#34;, choices=[&#34;true&#34;,&#34;false&#34;])
configcrud.add_argument(&#34;--fzf&#34;, dest=&#34;fzf&#34;, nargs=1, action=self._store_type, help=&#34;Use fzf for lists&#34;, choices=[&#34;true&#34;,&#34;false&#34;]) configcrud.add_argument(&#34;--fzf&#34;, dest=&#34;fzf&#34;, nargs=1, action=self._store_type, help=&#34;Use fzf for lists&#34;, choices=[&#34;true&#34;,&#34;false&#34;])
@ -1941,16 +2278,29 @@ Categorize the user&#39;s request based on the operation they want to perform on
configcrud.add_argument(&#34;--openai-api-key&#34;, dest=&#34;api_key&#34;, nargs=1, action=self._store_type, help=&#34;Set openai api_key&#34;, metavar=&#34;API_KEY&#34;) configcrud.add_argument(&#34;--openai-api-key&#34;, dest=&#34;api_key&#34;, nargs=1, action=self._store_type, help=&#34;Set openai api_key&#34;, metavar=&#34;API_KEY&#34;)
configcrud.add_argument(&#34;--openai-model&#34;, dest=&#34;model&#34;, nargs=1, action=self._store_type, help=&#34;Set openai model&#34;, metavar=&#34;MODEL&#34;) configcrud.add_argument(&#34;--openai-model&#34;, dest=&#34;model&#34;, nargs=1, action=self._store_type, help=&#34;Set openai model&#34;, metavar=&#34;MODEL&#34;)
configparser.set_defaults(func=self._func_others) configparser.set_defaults(func=self._func_others)
#Add plugins
file_path = self.config.defaultdir + &#34;/plugins&#34;
self.plugins = Plugins()
self.plugins._import_plugins_to_argparse(file_path, subparsers)
#Generate helps
nodeparser.usage = self._help(&#34;usage&#34;, subparsers)
nodeparser.epilog = self._help(&#34;end&#34;, subparsers)
nodeparser.help = self._help(&#34;node&#34;)
#Manage sys arguments #Manage sys arguments
commands = [&#34;node&#34;, &#34;profile&#34;, &#34;mv&#34;, &#34;move&#34;,&#34;copy&#34;, &#34;cp&#34;, &#34;bulk&#34;, &#34;ls&#34;, &#34;list&#34;, &#34;run&#34;, &#34;config&#34;, &#34;api&#34;, &#34;ai&#34;, &#34;export&#34;, &#34;import&#34;] self.commands = list(subparsers.choices.keys())
profilecmds = [&#34;--add&#34;, &#34;-a&#34;, &#34;--del&#34;, &#34;--rm&#34;, &#34;-r&#34;, &#34;--mod&#34;, &#34;--edit&#34;, &#34;-e&#34;, &#34;--show&#34;, &#34;-s&#34;] profilecmds = []
for action in profileparser._actions:
profilecmds.extend(action.option_strings)
if len(argv) &gt;= 2 and argv[1] == &#34;profile&#34; and argv[0] in profilecmds: if len(argv) &gt;= 2 and argv[1] == &#34;profile&#34; and argv[0] in profilecmds:
argv[1] = argv[0] argv[1] = argv[0]
argv[0] = &#34;profile&#34; argv[0] = &#34;profile&#34;
if len(argv) &lt; 1 or argv[0] not in commands: if len(argv) &lt; 1 or argv[0] not in self.commands:
argv.insert(0,&#34;node&#34;) argv.insert(0,&#34;node&#34;)
args = defaultparser.parse_args(argv) args = defaultparser.parse_args(argv)
return args.func(args) if args.subcommand in self.plugins.plugins:
self.plugins.plugins[args.subcommand].Entrypoint(args, self.plugins.plugin_parsers[args.subcommand].parser, self)
else:
return args.func(args)
class _store_type(argparse.Action): class _store_type(argparse.Action):
#Custom store type for cli app. #Custom store type for cli app.
@ -2373,7 +2723,7 @@ Categorize the user&#39;s request based on the operation they want to perform on
if not os.path.isdir(args.data[0]): if not os.path.isdir(args.data[0]):
raise argparse.ArgumentTypeError(f&#34;readable_dir:{args.data[0]} is not a valid path&#34;) raise argparse.ArgumentTypeError(f&#34;readable_dir:{args.data[0]} is not a valid path&#34;)
else: else:
pathfile = defaultdir + &#34;/.folder&#34; pathfile = self.config.defaultdir + &#34;/.folder&#34;
folder = os.path.abspath(args.data[0]).rstrip(&#39;/&#39;) folder = os.path.abspath(args.data[0]).rstrip(&#39;/&#39;)
with open(pathfile, &#34;w&#34;) as f: with open(pathfile, &#34;w&#34;) as f:
f.write(str(folder)) f.write(str(folder))
@ -2393,9 +2743,106 @@ Categorize the user&#39;s request based on the operation they want to perform on
self.config._saveconfig(self.config.file) self.config._saveconfig(self.config.file)
print(&#34;Config saved&#34;) print(&#34;Config saved&#34;)
def _func_plugin(self, args):
if args.add:
if not os.path.exists(args.add[1]):
print(&#34;File {} dosn&#39;t exists.&#34;.format(args.add[1]))
exit(14)
if args.add[0].isalpha() and args.add[0].islower() and len(args.add[0]) &lt;= 15:
disabled_dest_file = os.path.join(self.config.defaultdir + &#34;/plugins&#34;, args.add[0] + &#34;.py.bkp&#34;)
if args.add[0] in self.commands or os.path.exists(disabled_dest_file):
print(&#34;Plugin name can&#39;t be the same as other commands.&#34;)
exit(15)
else:
check_bad_script = self.plugins.verify_script(args.add[1])
if check_bad_script:
print(check_bad_script)
exit(16)
else:
try:
dest_file = os.path.join(self.config.defaultdir + &#34;/plugins&#34;, args.add[0] + &#34;.py&#34;)
shutil.copy2(args.add[1], dest_file)
print(f&#34;Plugin {args.add[0]} added succesfully.&#34;)
except:
print(&#34;Failed importing plugin file.&#34;)
exit(17)
else:
print(&#34;Plugin name should be lowercase letters up to 15 characters.&#34;)
exit(15)
elif args.delete:
plugin_file = os.path.join(self.config.defaultdir + &#34;/plugins&#34;, args.delete[0] + &#34;.py&#34;)
disabled_plugin_file = os.path.join(self.config.defaultdir + &#34;/plugins&#34;, args.delete[0] + &#34;.py.bkp&#34;)
plugin_exist = os.path.exists(plugin_file)
disabled_plugin_exist = os.path.exists(disabled_plugin_file)
if not plugin_exist and not disabled_plugin_exist:
print(&#34;Plugin {} dosn&#39;t exist.&#34;.format(args.delete[0]))
exit(14)
question = [inquirer.Confirm(&#34;delete&#34;, message=&#34;Are you sure you want to delete {} plugin?&#34;.format(args.delete[0]))]
confirm = inquirer.prompt(question)
if confirm == None:
exit(7)
if confirm[&#34;delete&#34;]:
try:
if plugin_exist:
os.remove(plugin_file)
elif disabled_plugin_exist:
os.remove(disabled_plugin_file)
print(f&#34;plugin {args.delete[0]} deleted succesfully.&#34;)
except:
print(&#34;Failed deleting plugin file.&#34;)
exit(17)
elif args.disable:
plugin_file = os.path.join(self.config.defaultdir + &#34;/plugins&#34;, args.disable[0] + &#34;.py&#34;)
disabled_plugin_file = os.path.join(self.config.defaultdir + &#34;/plugins&#34;, args.disable[0] + &#34;.py.bkp&#34;)
if not os.path.exists(plugin_file) or os.path.exists(disabled_plugin_file):
print(&#34;Plugin {} dosn&#39;t exist or it&#39;s disabled.&#34;.format(args.disable[0]))
exit(14)
try:
os.rename(plugin_file, disabled_plugin_file)
print(f&#34;plugin {args.disable[0]} disabled succesfully.&#34;)
except:
print(&#34;Failed disabling plugin file.&#34;)
exit(17)
elif args.enable:
plugin_file = os.path.join(self.config.defaultdir + &#34;/plugins&#34;, args.enable[0] + &#34;.py&#34;)
disabled_plugin_file = os.path.join(self.config.defaultdir + &#34;/plugins&#34;, args.enable[0] + &#34;.py.bkp&#34;)
if os.path.exists(plugin_file) or not os.path.exists(disabled_plugin_file):
print(&#34;Plugin {} dosn&#39;t exist or it&#39;s enabled.&#34;.format(args.enable[0]))
exit(14)
try:
os.rename(disabled_plugin_file, plugin_file)
print(f&#34;plugin {args.enable[0]} enabled succesfully.&#34;)
except:
print(&#34;Failed enabling plugin file.&#34;)
exit(17)
elif args.list:
enabled_files = []
disabled_files = []
plugins = {}
# Iterate over all files in the specified folder
for file in os.listdir(self.config.defaultdir + &#34;/plugins&#34;):
# Check if the file is a Python file
if file.endswith(&#39;.py&#39;):
enabled_files.append(os.path.splitext(file)[0])
# Check if the file is a Python backup file
elif file.endswith(&#39;.py.bkp&#39;):
disabled_files.append(os.path.splitext(os.path.splitext(file)[0])[0])
if enabled_files:
plugins[&#34;Enabled&#34;] = enabled_files
if disabled_files:
plugins[&#34;Disabled&#34;] = disabled_files
if plugins:
print(yaml.dump(plugins, sort_keys=False))
else:
print(&#34;There are no plugins added.&#34;)
def _func_import(self, args): def _func_import(self, args):
if not os.path.exists(args.data[0]): if not os.path.exists(args.data[0]):
print(&#34;File {} dosn&#39;t exists&#34;.format(args.data[0])) print(&#34;File {} dosn&#39;t exist&#34;.format(args.data[0]))
exit(14) exit(14)
print(&#34;This could overwrite your current configuration!&#34;) print(&#34;This could overwrite your current configuration!&#34;)
question = [inquirer.Confirm(&#34;import&#34;, message=&#34;Are you sure you want to import {} file?&#34;.format(args.data[0]))] question = [inquirer.Confirm(&#34;import&#34;, message=&#34;Are you sure you want to import {} file?&#34;.format(args.data[0]))]
@ -3037,14 +3484,30 @@ Categorize the user&#39;s request based on the operation they want to perform on
raise ValueError raise ValueError
return arg_value return arg_value
def _help(self, type): def _help(self, type, parsers = None):
#Store text for help and other commands #Store text for help and other commands
if type == &#34;node&#34;: if type == &#34;node&#34;:
return &#34;node[@subfolder][@folder]\nConnect to specific node or show all matching nodes\n[@subfolder][@folder]\nShow all available connections globally or in specified path&#34; return &#34;node[@subfolder][@folder]\nConnect to specific node or show all matching nodes\n[@subfolder][@folder]\nShow all available connections globally or in specified path&#34;
if type == &#34;usage&#34;: if type == &#34;usage&#34;:
return &#34;conn [-h] [--add | --del | --mod | --show | --debug] [node|folder] [--sftp]\n conn {profile,move,copy,list,bulk,export,import,run,config,api,ai} ...&#34; commands = []
for subcommand, subparser in parsers.choices.items():
if subparser.description != None:
commands.append(subcommand)
commands = &#34;,&#34;.join(commands)
usage_help = f&#34;conn [-h] [--add | --del | --mod | --show | --debug] [node|folder] [--sftp]\n conn {{{commands}}} ...&#34;
return usage_help
if type == &#34;end&#34;: if type == &#34;end&#34;:
return &#34;Commands:\n profile Manage profiles\n move (mv) Move node\n copy (cp) Copy node\n list (ls) List profiles, nodes or folders\n bulk Add nodes in bulk\n export Export connection folder to Yaml file\n import Import connection folder to config from Yaml file\n run Run scripts or commands on nodes\n config Manage app config\n api Start and stop connpy api\n ai Make request to an AI&#34; help_dict = {}
for subcommand, subparser in parsers.choices.items():
if subparser.description == None and help_dict:
previous_key = next(reversed(help_dict.keys()))
help_dict[f&#34;{previous_key}({subcommand})&#34;] = help_dict.pop(previous_key)
else:
help_dict[subcommand] = subparser.description
subparser.description = None
commands_help = &#34;Commands:\n&#34;
commands_help += &#34;\n&#34;.join([f&#34; {cmd:&lt;15} {help_text}&#34; for cmd, help_text in help_dict.items() if help_text != None])
return commands_help
if type == &#34;bashcompletion&#34;: if type == &#34;bashcompletion&#34;:
return &#39;&#39;&#39; return &#39;&#39;&#39;
#Here starts bash completion for conn #Here starts bash completion for conn
@ -3242,7 +3705,7 @@ tasks:
</details> </details>
</dd> </dd>
<dt id="connpy.connapp.start"><code class="name flex"> <dt id="connpy.connapp.start"><code class="name flex">
<span>def <span class="ident">start</span></span>(<span>self, argv=['connpy', '--html', '-o', 'docs/', '-f'])</span> <span>def <span class="ident">start</span></span>(<span>self, argv=['--html', 'connpy', '-o', 'docs', '--force'])</span>
</code></dt> </code></dt>
<dd> <dd>
<div class="desc"><h3 id="parameters">Parameters:</h3> <div class="desc"><h3 id="parameters">Parameters:</h3>
@ -3264,9 +3727,9 @@ tasks:
&#39;&#39;&#39; &#39;&#39;&#39;
#DEFAULTPARSER #DEFAULTPARSER
defaultparser = argparse.ArgumentParser(prog = &#34;conn&#34;, description = &#34;SSH and Telnet connection manager&#34;, formatter_class=argparse.RawTextHelpFormatter) defaultparser = argparse.ArgumentParser(prog = &#34;conn&#34;, description = &#34;SSH and Telnet connection manager&#34;, formatter_class=argparse.RawTextHelpFormatter)
subparsers = defaultparser.add_subparsers(title=&#34;Commands&#34;) subparsers = defaultparser.add_subparsers(title=&#34;Commands&#34;, dest=&#34;subcommand&#34;)
#NODEPARSER #NODEPARSER
nodeparser = subparsers.add_parser(&#34;node&#34;,usage=self._help(&#34;usage&#34;), help=self._help(&#34;node&#34;),epilog=self._help(&#34;end&#34;), formatter_class=argparse.RawTextHelpFormatter) nodeparser = subparsers.add_parser(&#34;node&#34;, formatter_class=argparse.RawTextHelpFormatter)
nodecrud = nodeparser.add_mutually_exclusive_group() nodecrud = nodeparser.add_mutually_exclusive_group()
nodeparser.add_argument(&#34;node&#34;, metavar=&#34;node|folder&#34;, nargs=&#39;?&#39;, default=None, action=self._store_type, help=self._help(&#34;node&#34;)) nodeparser.add_argument(&#34;node&#34;, metavar=&#34;node|folder&#34;, nargs=&#39;?&#39;, default=None, action=self._store_type, help=self._help(&#34;node&#34;))
nodecrud.add_argument(&#34;-v&#34;,&#34;--version&#34;, dest=&#34;action&#34;, action=&#34;store_const&#34;, help=&#34;Show version&#34;, const=&#34;version&#34;, default=&#34;connect&#34;) nodecrud.add_argument(&#34;-v&#34;,&#34;--version&#34;, dest=&#34;action&#34;, action=&#34;store_const&#34;, help=&#34;Show version&#34;, const=&#34;version&#34;, default=&#34;connect&#34;)
@ -3278,7 +3741,7 @@ tasks:
nodeparser.add_argument(&#34;-t&#34;,&#34;--sftp&#34;, dest=&#34;sftp&#34;, action=&#34;store_true&#34;, help=&#34;Connects using sftp instead of ssh&#34;) nodeparser.add_argument(&#34;-t&#34;,&#34;--sftp&#34;, dest=&#34;sftp&#34;, action=&#34;store_true&#34;, help=&#34;Connects using sftp instead of ssh&#34;)
nodeparser.set_defaults(func=self._func_node) nodeparser.set_defaults(func=self._func_node)
#PROFILEPARSER #PROFILEPARSER
profileparser = subparsers.add_parser(&#34;profile&#34;, help=&#34;Manage profiles&#34;) profileparser = subparsers.add_parser(&#34;profile&#34;, description=&#34;Manage profiles&#34;)
profileparser.add_argument(&#34;profile&#34;, nargs=1, action=self._store_type, type=self._type_profile, help=&#34;Name of profile to manage&#34;) profileparser.add_argument(&#34;profile&#34;, nargs=1, action=self._store_type, type=self._type_profile, help=&#34;Name of profile to manage&#34;)
profilecrud = profileparser.add_mutually_exclusive_group(required=True) profilecrud = profileparser.add_mutually_exclusive_group(required=True)
profilecrud.add_argument(&#34;-a&#34;, &#34;--add&#34;, dest=&#34;action&#34;, action=&#34;store_const&#34;, help=&#34;Add new profile&#34;, const=&#34;add&#34;) profilecrud.add_argument(&#34;-a&#34;, &#34;--add&#34;, dest=&#34;action&#34;, action=&#34;store_const&#34;, help=&#34;Add new profile&#34;, const=&#34;add&#34;)
@ -3287,53 +3750,62 @@ tasks:
profilecrud.add_argument(&#34;-s&#34;, &#34;--show&#34;, dest=&#34;action&#34;, action=&#34;store_const&#34;, help=&#34;Show profile&#34;, const=&#34;show&#34;) profilecrud.add_argument(&#34;-s&#34;, &#34;--show&#34;, dest=&#34;action&#34;, action=&#34;store_const&#34;, help=&#34;Show profile&#34;, const=&#34;show&#34;)
profileparser.set_defaults(func=self._func_profile) profileparser.set_defaults(func=self._func_profile)
#MOVEPARSER #MOVEPARSER
moveparser = subparsers.add_parser(&#34;move&#34;, aliases=[&#34;mv&#34;], help=&#34;Move node&#34;) moveparser = subparsers.add_parser(&#34;move&#34;, aliases=[&#34;mv&#34;], description=&#34;Move node&#34;)
moveparser.add_argument(&#34;move&#34;, nargs=2, action=self._store_type, help=&#34;Move node[@subfolder][@folder] dest_node[@subfolder][@folder]&#34;, default=&#34;move&#34;, type=self._type_node) moveparser.add_argument(&#34;move&#34;, nargs=2, action=self._store_type, help=&#34;Move node[@subfolder][@folder] dest_node[@subfolder][@folder]&#34;, default=&#34;move&#34;, type=self._type_node)
moveparser.set_defaults(func=self._func_others) moveparser.set_defaults(func=self._func_others)
#COPYPARSER #COPYPARSER
copyparser = subparsers.add_parser(&#34;copy&#34;, aliases=[&#34;cp&#34;], help=&#34;Copy node&#34;) copyparser = subparsers.add_parser(&#34;copy&#34;, aliases=[&#34;cp&#34;], description=&#34;Copy node&#34;)
copyparser.add_argument(&#34;cp&#34;, nargs=2, action=self._store_type, help=&#34;Copy node[@subfolder][@folder] new_node[@subfolder][@folder]&#34;, default=&#34;cp&#34;, type=self._type_node) copyparser.add_argument(&#34;cp&#34;, nargs=2, action=self._store_type, help=&#34;Copy node[@subfolder][@folder] new_node[@subfolder][@folder]&#34;, default=&#34;cp&#34;, type=self._type_node)
copyparser.set_defaults(func=self._func_others) copyparser.set_defaults(func=self._func_others)
#LISTPARSER #LISTPARSER
lsparser = subparsers.add_parser(&#34;list&#34;, aliases=[&#34;ls&#34;], help=&#34;List profiles, nodes or folders&#34;) lsparser = subparsers.add_parser(&#34;list&#34;, aliases=[&#34;ls&#34;], description=&#34;List profiles, nodes or folders&#34;)
lsparser.add_argument(&#34;ls&#34;, action=self._store_type, choices=[&#34;profiles&#34;,&#34;nodes&#34;,&#34;folders&#34;], help=&#34;List profiles, nodes or folders&#34;, default=False) lsparser.add_argument(&#34;ls&#34;, action=self._store_type, choices=[&#34;profiles&#34;,&#34;nodes&#34;,&#34;folders&#34;], help=&#34;List profiles, nodes or folders&#34;, default=False)
lsparser.add_argument(&#34;--filter&#34;, nargs=1, help=&#34;Filter results&#34;) lsparser.add_argument(&#34;--filter&#34;, nargs=1, help=&#34;Filter results&#34;)
lsparser.add_argument(&#34;--format&#34;, nargs=1, help=&#34;Format of the output of nodes using {name}, {NAME}, {location}, {LOCATION}, {host} and {HOST}&#34;) lsparser.add_argument(&#34;--format&#34;, nargs=1, help=&#34;Format of the output of nodes using {name}, {NAME}, {location}, {LOCATION}, {host} and {HOST}&#34;)
lsparser.set_defaults(func=self._func_others) lsparser.set_defaults(func=self._func_others)
#BULKPARSER #BULKPARSER
bulkparser = subparsers.add_parser(&#34;bulk&#34;, help=&#34;Add nodes in bulk&#34;) bulkparser = subparsers.add_parser(&#34;bulk&#34;, description=&#34;Add nodes in bulk&#34;)
bulkparser.add_argument(&#34;bulk&#34;, const=&#34;bulk&#34;, nargs=0, action=self._store_type, help=&#34;Add nodes in bulk&#34;) bulkparser.add_argument(&#34;bulk&#34;, const=&#34;bulk&#34;, nargs=0, action=self._store_type, help=&#34;Add nodes in bulk&#34;)
bulkparser.set_defaults(func=self._func_others) bulkparser.set_defaults(func=self._func_others)
# EXPORTPARSER # EXPORTPARSER
exportparser = subparsers.add_parser(&#34;export&#34;, help=&#34;Export connection folder to Yaml file&#34;) exportparser = subparsers.add_parser(&#34;export&#34;, description=&#34;Export connection folder to Yaml file&#34;)
exportparser.add_argument(&#34;export&#34;, nargs=&#34;+&#34;, action=self._store_type, help=&#34;Export /path/to/file.yml [@subfolder1][@folder1] [@subfolderN][@folderN]&#34;) exportparser.add_argument(&#34;export&#34;, nargs=&#34;+&#34;, action=self._store_type, help=&#34;Export /path/to/file.yml [@subfolder1][@folder1] [@subfolderN][@folderN]&#34;)
exportparser.set_defaults(func=self._func_export) exportparser.set_defaults(func=self._func_export)
# IMPORTPARSER # IMPORTPARSER
importparser = subparsers.add_parser(&#34;import&#34;, help=&#34;Import connection folder to config from Yaml file&#34;) importparser = subparsers.add_parser(&#34;import&#34;, description=&#34;Import connection folder to config from Yaml file&#34;)
importparser.add_argument(&#34;file&#34;, nargs=1, action=self._store_type, help=&#34;Import /path/to/file.yml&#34;) importparser.add_argument(&#34;file&#34;, nargs=1, action=self._store_type, help=&#34;Import /path/to/file.yml&#34;)
importparser.set_defaults(func=self._func_import) importparser.set_defaults(func=self._func_import)
# AIPARSER # AIPARSER
aiparser = subparsers.add_parser(&#34;ai&#34;, help=&#34;Make request to an AI&#34;) aiparser = subparsers.add_parser(&#34;ai&#34;, description=&#34;Make request to an AI&#34;)
aiparser.add_argument(&#34;ask&#34;, nargs=&#39;*&#39;, help=&#34;Ask connpy AI something&#34;) aiparser.add_argument(&#34;ask&#34;, nargs=&#39;*&#39;, help=&#34;Ask connpy AI something&#34;)
aiparser.add_argument(&#34;--model&#34;, nargs=1, help=&#34;Set the OPENAI model id&#34;) aiparser.add_argument(&#34;--model&#34;, nargs=1, help=&#34;Set the OPENAI model id&#34;)
aiparser.add_argument(&#34;--org&#34;, nargs=1, help=&#34;Set the OPENAI organization id&#34;) aiparser.add_argument(&#34;--org&#34;, nargs=1, help=&#34;Set the OPENAI organization id&#34;)
aiparser.add_argument(&#34;--api_key&#34;, nargs=1, help=&#34;Set the OPENAI API key&#34;) aiparser.add_argument(&#34;--api_key&#34;, nargs=1, help=&#34;Set the OPENAI API key&#34;)
aiparser.set_defaults(func=self._func_ai) aiparser.set_defaults(func=self._func_ai)
#RUNPARSER #RUNPARSER
runparser = subparsers.add_parser(&#34;run&#34;, help=&#34;Run scripts or commands on nodes&#34;, formatter_class=argparse.RawTextHelpFormatter) runparser = subparsers.add_parser(&#34;run&#34;, description=&#34;Run scripts or commands on nodes&#34;, formatter_class=argparse.RawTextHelpFormatter)
runparser.add_argument(&#34;run&#34;, nargs=&#39;+&#39;, action=self._store_type, help=self._help(&#34;run&#34;), default=&#34;run&#34;) runparser.add_argument(&#34;run&#34;, nargs=&#39;+&#39;, action=self._store_type, help=self._help(&#34;run&#34;), default=&#34;run&#34;)
runparser.add_argument(&#34;-g&#34;,&#34;--generate&#34;, dest=&#34;action&#34;, action=&#34;store_const&#34;, help=&#34;Generate yaml file template&#34;, const=&#34;generate&#34;, default=&#34;run&#34;) runparser.add_argument(&#34;-g&#34;,&#34;--generate&#34;, dest=&#34;action&#34;, action=&#34;store_const&#34;, help=&#34;Generate yaml file template&#34;, const=&#34;generate&#34;, default=&#34;run&#34;)
runparser.set_defaults(func=self._func_run) runparser.set_defaults(func=self._func_run)
#APIPARSER #APIPARSER
apiparser = subparsers.add_parser(&#34;api&#34;, help=&#34;Start and stop connpy api&#34;) apiparser = subparsers.add_parser(&#34;api&#34;, description=&#34;Start and stop connpy api&#34;)
apicrud = apiparser.add_mutually_exclusive_group(required=True) apicrud = apiparser.add_mutually_exclusive_group(required=True)
apicrud.add_argument(&#34;-s&#34;,&#34;--start&#34;, dest=&#34;start&#34;, nargs=&#34;?&#34;, action=self._store_type, help=&#34;Start conppy api&#34;, type=int, default=8048, metavar=&#34;PORT&#34;) apicrud.add_argument(&#34;-s&#34;,&#34;--start&#34;, dest=&#34;start&#34;, nargs=&#34;?&#34;, action=self._store_type, help=&#34;Start conppy api&#34;, type=int, default=8048, metavar=&#34;PORT&#34;)
apicrud.add_argument(&#34;-r&#34;,&#34;--restart&#34;, dest=&#34;restart&#34;, nargs=0, action=self._store_type, help=&#34;Restart conppy api&#34;) apicrud.add_argument(&#34;-r&#34;,&#34;--restart&#34;, dest=&#34;restart&#34;, nargs=0, action=self._store_type, help=&#34;Restart conppy api&#34;)
apicrud.add_argument(&#34;-x&#34;,&#34;--stop&#34;, dest=&#34;stop&#34;, nargs=0, action=self._store_type, help=&#34;Stop conppy api&#34;) apicrud.add_argument(&#34;-x&#34;,&#34;--stop&#34;, dest=&#34;stop&#34;, nargs=0, action=self._store_type, help=&#34;Stop conppy api&#34;)
apicrud.add_argument(&#34;-d&#34;, &#34;--debug&#34;, dest=&#34;debug&#34;, nargs=&#34;?&#34;, action=self._store_type, help=&#34;Run connpy server on debug mode&#34;, type=int, default=8048, metavar=&#34;PORT&#34;) apicrud.add_argument(&#34;-d&#34;, &#34;--debug&#34;, dest=&#34;debug&#34;, nargs=&#34;?&#34;, action=self._store_type, help=&#34;Run connpy server on debug mode&#34;, type=int, default=8048, metavar=&#34;PORT&#34;)
apiparser.set_defaults(func=self._func_api) apiparser.set_defaults(func=self._func_api)
#PLUGINSPARSER
pluginparser = subparsers.add_parser(&#34;plugin&#34;, description=&#34;Manage plugins&#34;)
plugincrud = pluginparser.add_mutually_exclusive_group(required=True)
plugincrud.add_argument(&#34;--add&#34;, metavar=(&#34;PLUGIN&#34;, &#34;FILE&#34;), nargs=2, help=&#34;Add new plugin&#34;)
plugincrud.add_argument(&#34;--del&#34;, dest=&#34;delete&#34;, metavar=&#34;PLUGIN&#34;, nargs=1, help=&#34;Delete plugin&#34;)
plugincrud.add_argument(&#34;--enable&#34;, metavar=&#34;PLUGIN&#34;, nargs=1, help=&#34;Enable plugin&#34;)
plugincrud.add_argument(&#34;--disable&#34;, metavar=&#34;PLUGIN&#34;, nargs=1, help=&#34;Disable plugin&#34;)
plugincrud.add_argument(&#34;--list&#34;, dest=&#34;list&#34;, action=&#34;store_true&#34;, help=&#34;Disable plugin&#34;)
pluginparser.set_defaults(func=self._func_plugin)
#CONFIGPARSER #CONFIGPARSER
configparser = subparsers.add_parser(&#34;config&#34;, help=&#34;Manage app config&#34;) configparser = subparsers.add_parser(&#34;config&#34;, description=&#34;Manage app config&#34;)
configcrud = configparser.add_mutually_exclusive_group(required=True) configcrud = configparser.add_mutually_exclusive_group(required=True)
configcrud.add_argument(&#34;--allow-uppercase&#34;, dest=&#34;case&#34;, nargs=1, action=self._store_type, help=&#34;Allow case sensitive names&#34;, choices=[&#34;true&#34;,&#34;false&#34;]) configcrud.add_argument(&#34;--allow-uppercase&#34;, dest=&#34;case&#34;, nargs=1, action=self._store_type, help=&#34;Allow case sensitive names&#34;, choices=[&#34;true&#34;,&#34;false&#34;])
configcrud.add_argument(&#34;--fzf&#34;, dest=&#34;fzf&#34;, nargs=1, action=self._store_type, help=&#34;Use fzf for lists&#34;, choices=[&#34;true&#34;,&#34;false&#34;]) configcrud.add_argument(&#34;--fzf&#34;, dest=&#34;fzf&#34;, nargs=1, action=self._store_type, help=&#34;Use fzf for lists&#34;, choices=[&#34;true&#34;,&#34;false&#34;])
@ -3344,16 +3816,29 @@ tasks:
configcrud.add_argument(&#34;--openai-api-key&#34;, dest=&#34;api_key&#34;, nargs=1, action=self._store_type, help=&#34;Set openai api_key&#34;, metavar=&#34;API_KEY&#34;) configcrud.add_argument(&#34;--openai-api-key&#34;, dest=&#34;api_key&#34;, nargs=1, action=self._store_type, help=&#34;Set openai api_key&#34;, metavar=&#34;API_KEY&#34;)
configcrud.add_argument(&#34;--openai-model&#34;, dest=&#34;model&#34;, nargs=1, action=self._store_type, help=&#34;Set openai model&#34;, metavar=&#34;MODEL&#34;) configcrud.add_argument(&#34;--openai-model&#34;, dest=&#34;model&#34;, nargs=1, action=self._store_type, help=&#34;Set openai model&#34;, metavar=&#34;MODEL&#34;)
configparser.set_defaults(func=self._func_others) configparser.set_defaults(func=self._func_others)
#Add plugins
file_path = self.config.defaultdir + &#34;/plugins&#34;
self.plugins = Plugins()
self.plugins._import_plugins_to_argparse(file_path, subparsers)
#Generate helps
nodeparser.usage = self._help(&#34;usage&#34;, subparsers)
nodeparser.epilog = self._help(&#34;end&#34;, subparsers)
nodeparser.help = self._help(&#34;node&#34;)
#Manage sys arguments #Manage sys arguments
commands = [&#34;node&#34;, &#34;profile&#34;, &#34;mv&#34;, &#34;move&#34;,&#34;copy&#34;, &#34;cp&#34;, &#34;bulk&#34;, &#34;ls&#34;, &#34;list&#34;, &#34;run&#34;, &#34;config&#34;, &#34;api&#34;, &#34;ai&#34;, &#34;export&#34;, &#34;import&#34;] self.commands = list(subparsers.choices.keys())
profilecmds = [&#34;--add&#34;, &#34;-a&#34;, &#34;--del&#34;, &#34;--rm&#34;, &#34;-r&#34;, &#34;--mod&#34;, &#34;--edit&#34;, &#34;-e&#34;, &#34;--show&#34;, &#34;-s&#34;] profilecmds = []
for action in profileparser._actions:
profilecmds.extend(action.option_strings)
if len(argv) &gt;= 2 and argv[1] == &#34;profile&#34; and argv[0] in profilecmds: if len(argv) &gt;= 2 and argv[1] == &#34;profile&#34; and argv[0] in profilecmds:
argv[1] = argv[0] argv[1] = argv[0]
argv[0] = &#34;profile&#34; argv[0] = &#34;profile&#34;
if len(argv) &lt; 1 or argv[0] not in commands: if len(argv) &lt; 1 or argv[0] not in self.commands:
argv.insert(0,&#34;node&#34;) argv.insert(0,&#34;node&#34;)
args = defaultparser.parse_args(argv) args = defaultparser.parse_args(argv)
return args.func(args)</code></pre> if args.subcommand in self.plugins.plugins:
self.plugins.plugins[args.subcommand].Entrypoint(args, self.plugins.plugin_parsers[args.subcommand].parser, self)
else:
return args.func(args)</code></pre>
</details> </details>
</dd> </dd>
</dl> </dl>
@ -4818,6 +5303,14 @@ tasks:
<li><a href="#examples">Examples</a></li> <li><a href="#examples">Examples</a></li>
</ul> </ul>
</li> </li>
<li><a href="#plugin-requirements-for-connpy">Plugin Requirements for Connpy</a><ul>
<li><a href="#general-structure">General Structure</a></li>
<li><a href="#specific-class-requirements">Specific Class Requirements</a></li>
<li><a href="#executable-block">Executable Block</a></li>
<li><a href="#script-verification">Script Verification</a></li>
<li><a href="#example-script">Example Script</a></li>
</ul>
</li>
<li><a href="#http-api">http API</a><ul> <li><a href="#http-api">http API</a><ul>
<li><a href="#1-list-nodes">1. List Nodes</a><ul> <li><a href="#1-list-nodes">1. List Nodes</a><ul>
<li><a href="#request-body">Request Body:</a></li> <li><a href="#request-body">Request Body:</a></li>
@ -4855,6 +5348,12 @@ tasks:
<li><h3><a href="#header-classes">Classes</a></h3> <li><h3><a href="#header-classes">Classes</a></h3>
<ul> <ul>
<li> <li>
<h4><code><a title="connpy.Plugins" href="#connpy.Plugins">Plugins</a></code></h4>
<ul class="">
<li><code><a title="connpy.Plugins.verify_script" href="#connpy.Plugins.verify_script">verify_script</a></code></li>
</ul>
</li>
<li>
<h4><code><a title="connpy.ai" href="#connpy.ai">ai</a></code></h4> <h4><code><a title="connpy.ai" href="#connpy.ai">ai</a></code></h4>
<ul class=""> <ul class="">
<li><code><a title="connpy.ai.ask" href="#connpy.ai.ask">ask</a></code></li> <li><code><a title="connpy.ai.ask" href="#connpy.ai.ask">ask</a></code></li>