feat: major architectural refactor to 5.1b1 - Service Layer, gRPC & Agent evolution (fragmented secrets)

This commit is contained in:
2026-04-17 18:42:08 -03:00
parent 85b23526cd
commit cb926c2b85
123 changed files with 38189 additions and 4640 deletions
+375
View File
@@ -0,0 +1,375 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.5">
<title>connpy.cli.ai_handler API documentation</title>
<meta name="description" content="">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source > summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible;min-width:max-content}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin:1em 0}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
/* Collapse source docstrings */
setTimeout(() => {
[...document.querySelectorAll('.hljs.language-python > .hljs-string')]
.filter(el => el.innerHTML.length > 200 && ['"""', "'''"].includes(el.innerHTML.substring(0, 3)))
.forEach(el => {
let d = document.createElement('details');
d.classList.add('hljs-string');
d.innerHTML = '<summary>"""</summary>' + el.innerHTML.substring(3);
el.replaceWith(d);
});
}, 100);
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>connpy.cli.ai_handler</code></h1>
</header>
<section id="section-intro">
</section>
<section>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="connpy.cli.ai_handler.AIHandler"><code class="flex name class">
<span>class <span class="ident">AIHandler</span></span>
<span>(</span><span>app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class AIHandler:
def __init__(self, app):
self.app = app
def dispatch(self, args):
if args.list_sessions:
sessions = self.app.services.ai.list_sessions()
if not sessions:
printer.info(&#34;No saved AI sessions found.&#34;)
return
columns = [&#34;ID&#34;, &#34;Title&#34;, &#34;Created At&#34;, &#34;Model&#34;]
rows = [[s[&#34;id&#34;], s[&#34;title&#34;], s[&#34;created_at&#34;], s[&#34;model&#34;]] for s in sessions]
printer.table(&#34;AI Persisted Sessions&#34;, columns, rows)
return
if args.delete_session:
try:
self.app.services.ai.delete_session(args.delete_session[0])
printer.success(f&#34;Session {args.delete_session[0]} deleted.&#34;)
except Exception as e:
printer.error(str(e))
return
# Determinar session_id para retomar
session_id = None
if args.resume:
sessions = self.app.services.ai.list_sessions()
session_id = sessions[0][&#34;id&#34;] if sessions else None
if not session_id:
printer.warning(&#34;No previous session found to resume.&#34;)
elif args.session:
session_id = args.session[0]
# Configurar argumentos adicionales para el servicio de AI
# Prioridad: CLI Args &gt; Configuración Local
settings = self.app.services.config_svc.get_settings().get(&#34;ai&#34;, {})
arguments = {}
for key in [&#34;engineer_model&#34;, &#34;engineer_api_key&#34;, &#34;architect_model&#34;, &#34;architect_api_key&#34;]:
cli_val = getattr(args, key, None)
if cli_val:
arguments[key] = cli_val[0]
elif settings.get(key):
arguments[key] = settings.get(key)
# Check keys only if running in local mode (not remote)
if getattr(self.app.services, &#34;mode&#34;, &#34;local&#34;) == &#34;local&#34;:
if not arguments.get(&#34;engineer_api_key&#34;):
printer.error(&#34;Engineer API key not configured. The chat cannot start.&#34;)
printer.info(&#34;Use &#39;connpy config --engineer-api-key &lt;key&gt;&#39; to set it.&#34;)
sys.exit(1)
if not arguments.get(&#34;architect_api_key&#34;):
printer.warning(&#34;Architect API key not configured. Architect will be unavailable.&#34;)
printer.info(&#34;Use &#39;connpy config --architect-api-key &lt;key&gt;&#39; to enable it.&#34;)
# El resto de la interacción el CLI la maneja con el agente subyacente
self.app.myai = self.app.services.ai
self.ai_overrides = arguments
if args.ask:
self.single_question(args, session_id)
else:
self.interactive_chat(args, session_id)
def single_question(self, args, session_id):
query = &#34; &#34;.join(args.ask)
with console.status(&#34;[ai_status]Agent is thinking and analyzing...&#34;) as status:
result = self.app.myai.ask(query, status=status, debug=args.debug, session_id=session_id, trust=args.trust, **self.ai_overrides)
responder = result.get(&#34;responder&#34;, &#34;engineer&#34;)
border = &#34;architect&#34; if responder == &#34;architect&#34; else &#34;engineer&#34;
title = &#34;[architect][bold]Network Architect[/bold][/architect]&#34; if responder == &#34;architect&#34; else &#34;[engineer][bold]Network Engineer[/bold][/engineer]&#34;
if not result.get(&#34;streamed&#34;):
mdprint(Panel(Markdown(result[&#34;response&#34;]), title=title, border_style=border, expand=False))
if &#34;usage&#34; in result:
u = result[&#34;usage&#34;]
console.print(f&#34;[debug]Tokens: {u[&#39;total&#39;]} (Input: {u[&#39;input&#39;]}, Output: {u[&#39;output&#39;]})[/debug]&#34;)
console.print()
def interactive_chat(self, args, session_id):
history = None
if session_id:
session_data = self.app.myai.load_session_data(session_id)
if session_data:
history = session_data.get(&#34;history&#34;, [])
mdprint(Rule(title=f&#34;[header] Resuming Session: {session_data.get(&#39;title&#39;)} [/header]&#34;, style=&#34;border&#34;))
if history:
mdprint(f&#34;[debug]Analyzing {len(history)} previous messages...[/debug]\n&#34;)
else:
printer.error(f&#34;Could not load session {session_id}. Starting clean.&#34;)
if not history:
mdprint(Rule(style=&#34;engineer&#34;))
mdprint(Markdown(&#34;**Networking Expert Agent**: Hi! I&#39;m your assistant. I can help you diagnose issues, run commands, and manage your nodes.\nType &#39;exit&#39; to quit.\n&#34;))
mdprint(Rule(style=&#34;engineer&#34;))
while True:
try:
user_query = Prompt.ask(&#34;[user_prompt]User[/user_prompt]&#34;)
if not user_query.strip(): continue
if user_query.lower() in [&#39;exit&#39;, &#39;quit&#39;, &#39;bye&#39;]: break
with console.status(&#34;[ai_status]Agent is thinking...&#34;) as status:
result = self.app.myai.ask(user_query, chat_history=history, status=status, debug=args.debug, trust=args.trust, **self.ai_overrides)
new_history = result.get(&#34;chat_history&#34;)
if new_history is not None:
history = new_history
responder = result.get(&#34;responder&#34;, &#34;engineer&#34;)
border = &#34;architect&#34; if responder == &#34;architect&#34; else &#34;engineer&#34;
title = &#34;[architect][bold]Network Architect[/bold][/architect]&#34; if responder == &#34;architect&#34; else &#34;[engineer][bold]Network Engineer[/bold][/engineer]&#34;
if not result.get(&#34;streamed&#34;):
response_text = result.get(&#34;response&#34;, &#34;&#34;)
if response_text:
mdprint(Panel(Markdown(response_text), title=title, border_style=border, expand=False))
if &#34;usage&#34; in result:
u = result[&#34;usage&#34;]
console.print(f&#34;[debug]Tokens: {u[&#39;total&#39;]} (Input: {u[&#39;input&#39;]}, Output: {u[&#39;output&#39;]})[/debug]&#34;)
console.print()
except KeyboardInterrupt:
break</code></pre>
</details>
<div class="desc"></div>
<h3>Methods</h3>
<dl>
<dt id="connpy.cli.ai_handler.AIHandler.dispatch"><code class="name flex">
<span>def <span class="ident">dispatch</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def dispatch(self, args):
if args.list_sessions:
sessions = self.app.services.ai.list_sessions()
if not sessions:
printer.info(&#34;No saved AI sessions found.&#34;)
return
columns = [&#34;ID&#34;, &#34;Title&#34;, &#34;Created At&#34;, &#34;Model&#34;]
rows = [[s[&#34;id&#34;], s[&#34;title&#34;], s[&#34;created_at&#34;], s[&#34;model&#34;]] for s in sessions]
printer.table(&#34;AI Persisted Sessions&#34;, columns, rows)
return
if args.delete_session:
try:
self.app.services.ai.delete_session(args.delete_session[0])
printer.success(f&#34;Session {args.delete_session[0]} deleted.&#34;)
except Exception as e:
printer.error(str(e))
return
# Determinar session_id para retomar
session_id = None
if args.resume:
sessions = self.app.services.ai.list_sessions()
session_id = sessions[0][&#34;id&#34;] if sessions else None
if not session_id:
printer.warning(&#34;No previous session found to resume.&#34;)
elif args.session:
session_id = args.session[0]
# Configurar argumentos adicionales para el servicio de AI
# Prioridad: CLI Args &gt; Configuración Local
settings = self.app.services.config_svc.get_settings().get(&#34;ai&#34;, {})
arguments = {}
for key in [&#34;engineer_model&#34;, &#34;engineer_api_key&#34;, &#34;architect_model&#34;, &#34;architect_api_key&#34;]:
cli_val = getattr(args, key, None)
if cli_val:
arguments[key] = cli_val[0]
elif settings.get(key):
arguments[key] = settings.get(key)
# Check keys only if running in local mode (not remote)
if getattr(self.app.services, &#34;mode&#34;, &#34;local&#34;) == &#34;local&#34;:
if not arguments.get(&#34;engineer_api_key&#34;):
printer.error(&#34;Engineer API key not configured. The chat cannot start.&#34;)
printer.info(&#34;Use &#39;connpy config --engineer-api-key &lt;key&gt;&#39; to set it.&#34;)
sys.exit(1)
if not arguments.get(&#34;architect_api_key&#34;):
printer.warning(&#34;Architect API key not configured. Architect will be unavailable.&#34;)
printer.info(&#34;Use &#39;connpy config --architect-api-key &lt;key&gt;&#39; to enable it.&#34;)
# El resto de la interacción el CLI la maneja con el agente subyacente
self.app.myai = self.app.services.ai
self.ai_overrides = arguments
if args.ask:
self.single_question(args, session_id)
else:
self.interactive_chat(args, session_id)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.ai_handler.AIHandler.interactive_chat"><code class="name flex">
<span>def <span class="ident">interactive_chat</span></span>(<span>self, args, session_id)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def interactive_chat(self, args, session_id):
history = None
if session_id:
session_data = self.app.myai.load_session_data(session_id)
if session_data:
history = session_data.get(&#34;history&#34;, [])
mdprint(Rule(title=f&#34;[header] Resuming Session: {session_data.get(&#39;title&#39;)} [/header]&#34;, style=&#34;border&#34;))
if history:
mdprint(f&#34;[debug]Analyzing {len(history)} previous messages...[/debug]\n&#34;)
else:
printer.error(f&#34;Could not load session {session_id}. Starting clean.&#34;)
if not history:
mdprint(Rule(style=&#34;engineer&#34;))
mdprint(Markdown(&#34;**Networking Expert Agent**: Hi! I&#39;m your assistant. I can help you diagnose issues, run commands, and manage your nodes.\nType &#39;exit&#39; to quit.\n&#34;))
mdprint(Rule(style=&#34;engineer&#34;))
while True:
try:
user_query = Prompt.ask(&#34;[user_prompt]User[/user_prompt]&#34;)
if not user_query.strip(): continue
if user_query.lower() in [&#39;exit&#39;, &#39;quit&#39;, &#39;bye&#39;]: break
with console.status(&#34;[ai_status]Agent is thinking...&#34;) as status:
result = self.app.myai.ask(user_query, chat_history=history, status=status, debug=args.debug, trust=args.trust, **self.ai_overrides)
new_history = result.get(&#34;chat_history&#34;)
if new_history is not None:
history = new_history
responder = result.get(&#34;responder&#34;, &#34;engineer&#34;)
border = &#34;architect&#34; if responder == &#34;architect&#34; else &#34;engineer&#34;
title = &#34;[architect][bold]Network Architect[/bold][/architect]&#34; if responder == &#34;architect&#34; else &#34;[engineer][bold]Network Engineer[/bold][/engineer]&#34;
if not result.get(&#34;streamed&#34;):
response_text = result.get(&#34;response&#34;, &#34;&#34;)
if response_text:
mdprint(Panel(Markdown(response_text), title=title, border_style=border, expand=False))
if &#34;usage&#34; in result:
u = result[&#34;usage&#34;]
console.print(f&#34;[debug]Tokens: {u[&#39;total&#39;]} (Input: {u[&#39;input&#39;]}, Output: {u[&#39;output&#39;]})[/debug]&#34;)
console.print()
except KeyboardInterrupt:
break</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.ai_handler.AIHandler.single_question"><code class="name flex">
<span>def <span class="ident">single_question</span></span>(<span>self, args, session_id)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def single_question(self, args, session_id):
query = &#34; &#34;.join(args.ask)
with console.status(&#34;[ai_status]Agent is thinking and analyzing...&#34;) as status:
result = self.app.myai.ask(query, status=status, debug=args.debug, session_id=session_id, trust=args.trust, **self.ai_overrides)
responder = result.get(&#34;responder&#34;, &#34;engineer&#34;)
border = &#34;architect&#34; if responder == &#34;architect&#34; else &#34;engineer&#34;
title = &#34;[architect][bold]Network Architect[/bold][/architect]&#34; if responder == &#34;architect&#34; else &#34;[engineer][bold]Network Engineer[/bold][/engineer]&#34;
if not result.get(&#34;streamed&#34;):
mdprint(Panel(Markdown(result[&#34;response&#34;]), title=title, border_style=border, expand=False))
if &#34;usage&#34; in result:
u = result[&#34;usage&#34;]
console.print(f&#34;[debug]Tokens: {u[&#39;total&#39;]} (Input: {u[&#39;input&#39;]}, Output: {u[&#39;output&#39;]})[/debug]&#34;)
console.print()</code></pre>
</details>
<div class="desc"></div>
</dd>
</dl>
</dd>
</dl>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="connpy.cli" href="index.html">connpy.cli</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="connpy.cli.ai_handler.AIHandler" href="#connpy.cli.ai_handler.AIHandler">AIHandler</a></code></h4>
<ul class="">
<li><code><a title="connpy.cli.ai_handler.AIHandler.dispatch" href="#connpy.cli.ai_handler.AIHandler.dispatch">dispatch</a></code></li>
<li><code><a title="connpy.cli.ai_handler.AIHandler.interactive_chat" href="#connpy.cli.ai_handler.AIHandler.interactive_chat">interactive_chat</a></code></li>
<li><code><a title="connpy.cli.ai_handler.AIHandler.single_question" href="#connpy.cli.ai_handler.AIHandler.single_question">single_question</a></code></li>
</ul>
</li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.5</a>.</p>
</footer>
</body>
</html>
+199
View File
@@ -0,0 +1,199 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.5">
<title>connpy.cli.api_handler API documentation</title>
<meta name="description" content="">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source > summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible;min-width:max-content}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin:1em 0}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
/* Collapse source docstrings */
setTimeout(() => {
[...document.querySelectorAll('.hljs.language-python > .hljs-string')]
.filter(el => el.innerHTML.length > 200 && ['"""', "'''"].includes(el.innerHTML.substring(0, 3)))
.forEach(el => {
let d = document.createElement('details');
d.classList.add('hljs-string');
d.innerHTML = '<summary>"""</summary>' + el.innerHTML.substring(3);
el.replaceWith(d);
});
}, 100);
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>connpy.cli.api_handler</code></h1>
</header>
<section id="section-intro">
</section>
<section>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="connpy.cli.api_handler.APIHandler"><code class="flex name class">
<span>class <span class="ident">APIHandler</span></span>
<span>(</span><span>app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class APIHandler:
def __init__(self, app):
self.app = app
def dispatch(self, args):
try:
status = self.app.services.system.get_api_status()
if args.command == &#34;stop&#34;:
if not status[&#34;running&#34;]:
printer.warning(&#34;API does not seem to be running.&#34;)
else:
stopped = self.app.services.system.stop_api()
if stopped:
printer.success(&#34;API stopped successfully.&#34;)
elif args.command == &#34;restart&#34;:
port = args.data if args.data and isinstance(args.data, int) else None
if status[&#34;running&#34;]:
printer.info(f&#34;Stopping server with process ID {status[&#39;pid&#39;]}...&#34;)
# Service handles port preservation if port is None
self.app.services.system.restart_api(port=port)
if status[&#34;running&#34;]:
printer.info(f&#34;Server with process ID {status[&#39;pid&#39;]} stopped.&#34;)
# Re-fetch status to show the actual port used
new_status = self.app.services.system.get_api_status()
printer.success(f&#34;API restarted on port {new_status.get(&#39;port&#39;, &#39;unknown&#39;)}.&#34;)
elif args.command == &#34;start&#34;:
if status[&#34;running&#34;]:
msg = f&#34;Connpy server is already running (PID: {status[&#39;pid&#39;]}&#34;
if status.get(&#34;port&#34;):
msg += f&#34;, Port: {status[&#39;port&#39;]}&#34;
msg += &#34;).&#34;
printer.warning(msg)
else:
port = args.data if args.data and isinstance(args.data, int) else 8048
self.app.services.system.start_api(port=port)
printer.success(f&#34;API started on port {port}.&#34;)
elif args.command == &#34;debug&#34;:
port = args.data if args.data and isinstance(args.data, int) else 8048
self.app.services.system.debug_api(port=port)
except ConnpyError as e:
printer.error(str(e))
sys.exit(1)</code></pre>
</details>
<div class="desc"></div>
<h3>Methods</h3>
<dl>
<dt id="connpy.cli.api_handler.APIHandler.dispatch"><code class="name flex">
<span>def <span class="ident">dispatch</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def dispatch(self, args):
try:
status = self.app.services.system.get_api_status()
if args.command == &#34;stop&#34;:
if not status[&#34;running&#34;]:
printer.warning(&#34;API does not seem to be running.&#34;)
else:
stopped = self.app.services.system.stop_api()
if stopped:
printer.success(&#34;API stopped successfully.&#34;)
elif args.command == &#34;restart&#34;:
port = args.data if args.data and isinstance(args.data, int) else None
if status[&#34;running&#34;]:
printer.info(f&#34;Stopping server with process ID {status[&#39;pid&#39;]}...&#34;)
# Service handles port preservation if port is None
self.app.services.system.restart_api(port=port)
if status[&#34;running&#34;]:
printer.info(f&#34;Server with process ID {status[&#39;pid&#39;]} stopped.&#34;)
# Re-fetch status to show the actual port used
new_status = self.app.services.system.get_api_status()
printer.success(f&#34;API restarted on port {new_status.get(&#39;port&#39;, &#39;unknown&#39;)}.&#34;)
elif args.command == &#34;start&#34;:
if status[&#34;running&#34;]:
msg = f&#34;Connpy server is already running (PID: {status[&#39;pid&#39;]}&#34;
if status.get(&#34;port&#34;):
msg += f&#34;, Port: {status[&#39;port&#39;]}&#34;
msg += &#34;).&#34;
printer.warning(msg)
else:
port = args.data if args.data and isinstance(args.data, int) else 8048
self.app.services.system.start_api(port=port)
printer.success(f&#34;API started on port {port}.&#34;)
elif args.command == &#34;debug&#34;:
port = args.data if args.data and isinstance(args.data, int) else 8048
self.app.services.system.debug_api(port=port)
except ConnpyError as e:
printer.error(str(e))
sys.exit(1)</code></pre>
</details>
<div class="desc"></div>
</dd>
</dl>
</dd>
</dl>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="connpy.cli" href="index.html">connpy.cli</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="connpy.cli.api_handler.APIHandler" href="#connpy.cli.api_handler.APIHandler">APIHandler</a></code></h4>
<ul class="">
<li><code><a title="connpy.cli.api_handler.APIHandler.dispatch" href="#connpy.cli.api_handler.APIHandler.dispatch">dispatch</a></code></li>
</ul>
</li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.5</a>.</p>
</footer>
</body>
</html>
+488
View File
@@ -0,0 +1,488 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.5">
<title>connpy.cli.config_handler API documentation</title>
<meta name="description" content="">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source > summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible;min-width:max-content}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin:1em 0}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
/* Collapse source docstrings */
setTimeout(() => {
[...document.querySelectorAll('.hljs.language-python > .hljs-string')]
.filter(el => el.innerHTML.length > 200 && ['"""', "'''"].includes(el.innerHTML.substring(0, 3)))
.forEach(el => {
let d = document.createElement('details');
d.classList.add('hljs-string');
d.innerHTML = '<summary>"""</summary>' + el.innerHTML.substring(3);
el.replaceWith(d);
});
}, 100);
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>connpy.cli.config_handler</code></h1>
</header>
<section id="section-intro">
</section>
<section>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="connpy.cli.config_handler.ConfigHandler"><code class="flex name class">
<span>class <span class="ident">ConfigHandler</span></span>
<span>(</span><span>app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class ConfigHandler:
def __init__(self, app):
self.app = app
def dispatch(self, args):
actions = {
&#34;completion&#34;: self.show_completion,
&#34;fzf_wrapper&#34;: self.show_fzf_wrapper,
&#34;case&#34;: self.set_case,
&#34;fzf&#34;: self.set_fzf,
&#34;idletime&#34;: self.set_idletime,
&#34;configfolder&#34;: self.set_configfolder,
&#34;theme&#34;: self.set_theme,
&#34;engineer_model&#34;: self.set_ai_config,
&#34;engineer_api_key&#34;: self.set_ai_config,
&#34;architect_model&#34;: self.set_ai_config,
&#34;architect_api_key&#34;: self.set_ai_config,
&#34;trusted_commands&#34;: self.set_ai_config,
&#34;service_mode&#34;: self.set_service_mode,
&#34;remote_host&#34;: self.set_remote_host,
&#34;sync_remote&#34;: self.set_sync_remote
}
handler = actions.get(getattr(args, &#34;command&#34;, None))
if handler:
return handler(args)
# If no specific command was triggered, show current configuration
return self.show_config(args)
def show_config(self, args):
settings = self.app.services.config_svc.get_settings()
yaml_str = yaml.dump(settings, sort_keys=False, default_flow_style=False)
printer.data(&#34;Current Configuration&#34;, yaml_str)
def set_service_mode(self, args):
new_mode = args.data[0]
if new_mode == &#34;remote&#34;:
settings = self.app.services.config_svc.get_settings()
if not settings.get(&#34;remote_host&#34;):
printer.error(&#34;Remote host must be configured before switching to remote mode&#34;)
return
self.app.services.config_svc.update_setting(&#34;service_mode&#34;, new_mode)
# Immediate sync of fzf/text cache files for the new mode
try:
# 1. Clear old cache files to avoid discrepancies if fetch fails
self.app.config._generate_nodes_cache(nodes=[], folders=[], profiles=[])
# 2. Re-initialize services for the new mode
from ..services.provider import ServiceProvider
settings = self.app.services.config_svc.get_settings()
new_services = ServiceProvider(self.app.config, mode=new_mode, remote_host=settings.get(&#34;remote_host&#34;))
# 3. Fetch data from new mode and generate cache
nodes = new_services.nodes.list_nodes()
folders = new_services.nodes.list_folders()
profiles = new_services.profiles.list_profiles()
new_services.nodes.generate_cache(nodes=nodes, folders=folders, profiles=profiles)
printer.success(&#34;Config saved&#34;)
except Exception as e:
printer.success(&#34;Config saved&#34;)
printer.warning(f&#34;Note: Could not synchronize fzf cache: {e}&#34;)
def set_remote_host(self, args):
self.app.services.config_svc.update_setting(&#34;remote_host&#34;, args.data[0])
printer.success(&#34;Config saved&#34;)
def set_theme(self, args):
try:
valid_styles = self.app.services.config_svc.apply_theme_from_file(args.data[0])
# Apply immediately to current session
printer.apply_theme(valid_styles)
printer.success(f&#34;Theme &#39;{args.data[0]}&#39; applied and saved&#34;)
except (ConnpyError, InvalidConfigurationError) as e:
printer.error(str(e))
def show_fzf_wrapper(self, args):
print(get_instructions(&#34;fzf_wrapper_&#34; + args.data[0]))
def show_completion(self, args):
print(get_instructions(args.data[0] + &#34;completion&#34;))
def set_case(self, args):
val = (args.data[0].lower() == &#34;true&#34;)
self.app.services.config_svc.update_setting(&#34;case&#34;, val)
self.app.case = val
printer.success(&#34;Config saved&#34;)
def set_fzf(self, args):
val = (args.data[0].lower() == &#34;true&#34;)
self.app.services.config_svc.update_setting(&#34;fzf&#34;, val)
self.app.fzf = val
printer.success(&#34;Config saved&#34;)
def set_idletime(self, args):
try:
val = max(0, int(args.data[0]))
self.app.services.config_svc.update_setting(&#34;idletime&#34;, val)
printer.success(&#34;Config saved&#34;)
except ValueError:
printer.error(&#34;Keepalive must be an integer.&#34;)
def set_configfolder(self, args):
try:
self.app.services.config_svc.set_config_folder(args.data[0])
printer.success(&#34;Config saved&#34;)
except ConnpyError as e:
printer.error(str(e))
sys.exit(1)
def set_sync_remote(self, args):
val = (args.data[0].lower() == &#34;true&#34;)
self.app.services.config_svc.update_setting(&#34;sync_remote&#34;, val)
self.app.services.sync.sync_remote = val
printer.success(&#34;Config saved&#34;)
def set_ai_config(self, args):
try:
settings = self.app.services.config_svc.get_settings()
aiconfig = settings.get(&#34;ai&#34;, {})
aiconfig[args.command] = args.data[0]
self.app.services.config_svc.update_setting(&#34;ai&#34;, aiconfig)
printer.success(&#34;Config saved&#34;)
except ConnpyError as e:
printer.error(str(e))</code></pre>
</details>
<div class="desc"></div>
<h3>Methods</h3>
<dl>
<dt id="connpy.cli.config_handler.ConfigHandler.dispatch"><code class="name flex">
<span>def <span class="ident">dispatch</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def dispatch(self, args):
actions = {
&#34;completion&#34;: self.show_completion,
&#34;fzf_wrapper&#34;: self.show_fzf_wrapper,
&#34;case&#34;: self.set_case,
&#34;fzf&#34;: self.set_fzf,
&#34;idletime&#34;: self.set_idletime,
&#34;configfolder&#34;: self.set_configfolder,
&#34;theme&#34;: self.set_theme,
&#34;engineer_model&#34;: self.set_ai_config,
&#34;engineer_api_key&#34;: self.set_ai_config,
&#34;architect_model&#34;: self.set_ai_config,
&#34;architect_api_key&#34;: self.set_ai_config,
&#34;trusted_commands&#34;: self.set_ai_config,
&#34;service_mode&#34;: self.set_service_mode,
&#34;remote_host&#34;: self.set_remote_host,
&#34;sync_remote&#34;: self.set_sync_remote
}
handler = actions.get(getattr(args, &#34;command&#34;, None))
if handler:
return handler(args)
# If no specific command was triggered, show current configuration
return self.show_config(args)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.config_handler.ConfigHandler.set_ai_config"><code class="name flex">
<span>def <span class="ident">set_ai_config</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def set_ai_config(self, args):
try:
settings = self.app.services.config_svc.get_settings()
aiconfig = settings.get(&#34;ai&#34;, {})
aiconfig[args.command] = args.data[0]
self.app.services.config_svc.update_setting(&#34;ai&#34;, aiconfig)
printer.success(&#34;Config saved&#34;)
except ConnpyError as e:
printer.error(str(e))</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.config_handler.ConfigHandler.set_case"><code class="name flex">
<span>def <span class="ident">set_case</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def set_case(self, args):
val = (args.data[0].lower() == &#34;true&#34;)
self.app.services.config_svc.update_setting(&#34;case&#34;, val)
self.app.case = val
printer.success(&#34;Config saved&#34;)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.config_handler.ConfigHandler.set_configfolder"><code class="name flex">
<span>def <span class="ident">set_configfolder</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def set_configfolder(self, args):
try:
self.app.services.config_svc.set_config_folder(args.data[0])
printer.success(&#34;Config saved&#34;)
except ConnpyError as e:
printer.error(str(e))
sys.exit(1)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.config_handler.ConfigHandler.set_fzf"><code class="name flex">
<span>def <span class="ident">set_fzf</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def set_fzf(self, args):
val = (args.data[0].lower() == &#34;true&#34;)
self.app.services.config_svc.update_setting(&#34;fzf&#34;, val)
self.app.fzf = val
printer.success(&#34;Config saved&#34;)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.config_handler.ConfigHandler.set_idletime"><code class="name flex">
<span>def <span class="ident">set_idletime</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def set_idletime(self, args):
try:
val = max(0, int(args.data[0]))
self.app.services.config_svc.update_setting(&#34;idletime&#34;, val)
printer.success(&#34;Config saved&#34;)
except ValueError:
printer.error(&#34;Keepalive must be an integer.&#34;)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.config_handler.ConfigHandler.set_remote_host"><code class="name flex">
<span>def <span class="ident">set_remote_host</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def set_remote_host(self, args):
self.app.services.config_svc.update_setting(&#34;remote_host&#34;, args.data[0])
printer.success(&#34;Config saved&#34;)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.config_handler.ConfigHandler.set_service_mode"><code class="name flex">
<span>def <span class="ident">set_service_mode</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def set_service_mode(self, args):
new_mode = args.data[0]
if new_mode == &#34;remote&#34;:
settings = self.app.services.config_svc.get_settings()
if not settings.get(&#34;remote_host&#34;):
printer.error(&#34;Remote host must be configured before switching to remote mode&#34;)
return
self.app.services.config_svc.update_setting(&#34;service_mode&#34;, new_mode)
# Immediate sync of fzf/text cache files for the new mode
try:
# 1. Clear old cache files to avoid discrepancies if fetch fails
self.app.config._generate_nodes_cache(nodes=[], folders=[], profiles=[])
# 2. Re-initialize services for the new mode
from ..services.provider import ServiceProvider
settings = self.app.services.config_svc.get_settings()
new_services = ServiceProvider(self.app.config, mode=new_mode, remote_host=settings.get(&#34;remote_host&#34;))
# 3. Fetch data from new mode and generate cache
nodes = new_services.nodes.list_nodes()
folders = new_services.nodes.list_folders()
profiles = new_services.profiles.list_profiles()
new_services.nodes.generate_cache(nodes=nodes, folders=folders, profiles=profiles)
printer.success(&#34;Config saved&#34;)
except Exception as e:
printer.success(&#34;Config saved&#34;)
printer.warning(f&#34;Note: Could not synchronize fzf cache: {e}&#34;)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.config_handler.ConfigHandler.set_sync_remote"><code class="name flex">
<span>def <span class="ident">set_sync_remote</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def set_sync_remote(self, args):
val = (args.data[0].lower() == &#34;true&#34;)
self.app.services.config_svc.update_setting(&#34;sync_remote&#34;, val)
self.app.services.sync.sync_remote = val
printer.success(&#34;Config saved&#34;)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.config_handler.ConfigHandler.set_theme"><code class="name flex">
<span>def <span class="ident">set_theme</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def set_theme(self, args):
try:
valid_styles = self.app.services.config_svc.apply_theme_from_file(args.data[0])
# Apply immediately to current session
printer.apply_theme(valid_styles)
printer.success(f&#34;Theme &#39;{args.data[0]}&#39; applied and saved&#34;)
except (ConnpyError, InvalidConfigurationError) as e:
printer.error(str(e))</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.config_handler.ConfigHandler.show_completion"><code class="name flex">
<span>def <span class="ident">show_completion</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def show_completion(self, args):
print(get_instructions(args.data[0] + &#34;completion&#34;))</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.config_handler.ConfigHandler.show_config"><code class="name flex">
<span>def <span class="ident">show_config</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def show_config(self, args):
settings = self.app.services.config_svc.get_settings()
yaml_str = yaml.dump(settings, sort_keys=False, default_flow_style=False)
printer.data(&#34;Current Configuration&#34;, yaml_str)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.config_handler.ConfigHandler.show_fzf_wrapper"><code class="name flex">
<span>def <span class="ident">show_fzf_wrapper</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def show_fzf_wrapper(self, args):
print(get_instructions(&#34;fzf_wrapper_&#34; + args.data[0]))</code></pre>
</details>
<div class="desc"></div>
</dd>
</dl>
</dd>
</dl>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="connpy.cli" href="index.html">connpy.cli</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="connpy.cli.config_handler.ConfigHandler" href="#connpy.cli.config_handler.ConfigHandler">ConfigHandler</a></code></h4>
<ul class="two-column">
<li><code><a title="connpy.cli.config_handler.ConfigHandler.dispatch" href="#connpy.cli.config_handler.ConfigHandler.dispatch">dispatch</a></code></li>
<li><code><a title="connpy.cli.config_handler.ConfigHandler.set_ai_config" href="#connpy.cli.config_handler.ConfigHandler.set_ai_config">set_ai_config</a></code></li>
<li><code><a title="connpy.cli.config_handler.ConfigHandler.set_case" href="#connpy.cli.config_handler.ConfigHandler.set_case">set_case</a></code></li>
<li><code><a title="connpy.cli.config_handler.ConfigHandler.set_configfolder" href="#connpy.cli.config_handler.ConfigHandler.set_configfolder">set_configfolder</a></code></li>
<li><code><a title="connpy.cli.config_handler.ConfigHandler.set_fzf" href="#connpy.cli.config_handler.ConfigHandler.set_fzf">set_fzf</a></code></li>
<li><code><a title="connpy.cli.config_handler.ConfigHandler.set_idletime" href="#connpy.cli.config_handler.ConfigHandler.set_idletime">set_idletime</a></code></li>
<li><code><a title="connpy.cli.config_handler.ConfigHandler.set_remote_host" href="#connpy.cli.config_handler.ConfigHandler.set_remote_host">set_remote_host</a></code></li>
<li><code><a title="connpy.cli.config_handler.ConfigHandler.set_service_mode" href="#connpy.cli.config_handler.ConfigHandler.set_service_mode">set_service_mode</a></code></li>
<li><code><a title="connpy.cli.config_handler.ConfigHandler.set_sync_remote" href="#connpy.cli.config_handler.ConfigHandler.set_sync_remote">set_sync_remote</a></code></li>
<li><code><a title="connpy.cli.config_handler.ConfigHandler.set_theme" href="#connpy.cli.config_handler.ConfigHandler.set_theme">set_theme</a></code></li>
<li><code><a title="connpy.cli.config_handler.ConfigHandler.show_completion" href="#connpy.cli.config_handler.ConfigHandler.show_completion">show_completion</a></code></li>
<li><code><a title="connpy.cli.config_handler.ConfigHandler.show_config" href="#connpy.cli.config_handler.ConfigHandler.show_config">show_config</a></code></li>
<li><code><a title="connpy.cli.config_handler.ConfigHandler.show_fzf_wrapper" href="#connpy.cli.config_handler.ConfigHandler.show_fzf_wrapper">show_fzf_wrapper</a></code></li>
</ul>
</li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.5</a>.</p>
</footer>
</body>
</html>
+255
View File
@@ -0,0 +1,255 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.5">
<title>connpy.cli.context_handler API documentation</title>
<meta name="description" content="">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source > summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible;min-width:max-content}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin:1em 0}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
/* Collapse source docstrings */
setTimeout(() => {
[...document.querySelectorAll('.hljs.language-python > .hljs-string')]
.filter(el => el.innerHTML.length > 200 && ['"""', "'''"].includes(el.innerHTML.substring(0, 3)))
.forEach(el => {
let d = document.createElement('details');
d.classList.add('hljs-string');
d.innerHTML = '<summary>"""</summary>' + el.innerHTML.substring(3);
el.replaceWith(d);
});
}, 100);
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>connpy.cli.context_handler</code></h1>
</header>
<section id="section-intro">
</section>
<section>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="connpy.cli.context_handler.ContextHandler"><code class="flex name class">
<span>class <span class="ident">ContextHandler</span></span>
<span>(</span><span>app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class ContextHandler:
def __init__(self, app):
self.app = app
self.service = self.app.services.context
def dispatch(self, args):
try:
if args.add:
if len(args.add) &lt; 2:
printer.error(&#34;--add requires name and at least one regex&#34;)
return
self.service.add_context(args.add[0], args.add[1:])
printer.success(f&#34;Context &#39;{args.add[0]}&#39; added successfully.&#34;)
elif args.rm:
if not args.context_name:
printer.error(&#34;--rm requires a context name&#34;)
return
self.service.delete_context(args.context_name)
printer.success(f&#34;Context &#39;{args.context_name}&#39; deleted successfully.&#34;)
elif args.ls:
contexts = self.service.list_contexts()
for ctx in contexts:
if ctx[&#34;active&#34;]:
printer.success(f&#34;{ctx[&#39;name&#39;]} (active)&#34;)
else:
printer.custom(&#34; &#34;, ctx[&#34;name&#34;])
elif args.set:
if not args.context_name:
printer.error(&#34;--set requires a context name&#34;)
return
self.service.set_active_context(args.context_name)
printer.success(f&#34;Context set to: {args.context_name}&#34;)
elif args.show:
if not args.context_name:
printer.error(&#34;--show requires a context name&#34;)
return
contexts = self.service.contexts
if args.context_name not in contexts:
printer.error(f&#34;Context &#39;{args.context_name}&#39; does not exist&#34;)
return
yaml_output = yaml.dump(contexts[args.context_name], sort_keys=False, default_flow_style=False)
printer.custom(args.context_name, &#34;&#34;)
print(yaml_output)
elif args.edit:
if len(args.edit) &lt; 2:
printer.error(&#34;--edit requires name and at least one regex&#34;)
return
self.service.update_context(args.edit[0], args.edit[1:])
printer.success(f&#34;Context &#39;{args.edit[0]}&#39; modified successfully.&#34;)
else:
# Default behavior if no flags: show list
self.dispatch_ls(args)
except ValueError as e:
printer.error(str(e))
except ConnpyError as e:
printer.error(str(e))
sys.exit(1)
def dispatch_ls(self, args):
contexts = self.service.list_contexts()
for ctx in contexts:
if ctx[&#34;active&#34;]:
printer.success(f&#34;{ctx[&#39;name&#39;]} (active)&#34;)
else:
printer.custom(&#34; &#34;, ctx[&#34;name&#34;])</code></pre>
</details>
<div class="desc"></div>
<h3>Methods</h3>
<dl>
<dt id="connpy.cli.context_handler.ContextHandler.dispatch"><code class="name flex">
<span>def <span class="ident">dispatch</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def dispatch(self, args):
try:
if args.add:
if len(args.add) &lt; 2:
printer.error(&#34;--add requires name and at least one regex&#34;)
return
self.service.add_context(args.add[0], args.add[1:])
printer.success(f&#34;Context &#39;{args.add[0]}&#39; added successfully.&#34;)
elif args.rm:
if not args.context_name:
printer.error(&#34;--rm requires a context name&#34;)
return
self.service.delete_context(args.context_name)
printer.success(f&#34;Context &#39;{args.context_name}&#39; deleted successfully.&#34;)
elif args.ls:
contexts = self.service.list_contexts()
for ctx in contexts:
if ctx[&#34;active&#34;]:
printer.success(f&#34;{ctx[&#39;name&#39;]} (active)&#34;)
else:
printer.custom(&#34; &#34;, ctx[&#34;name&#34;])
elif args.set:
if not args.context_name:
printer.error(&#34;--set requires a context name&#34;)
return
self.service.set_active_context(args.context_name)
printer.success(f&#34;Context set to: {args.context_name}&#34;)
elif args.show:
if not args.context_name:
printer.error(&#34;--show requires a context name&#34;)
return
contexts = self.service.contexts
if args.context_name not in contexts:
printer.error(f&#34;Context &#39;{args.context_name}&#39; does not exist&#34;)
return
yaml_output = yaml.dump(contexts[args.context_name], sort_keys=False, default_flow_style=False)
printer.custom(args.context_name, &#34;&#34;)
print(yaml_output)
elif args.edit:
if len(args.edit) &lt; 2:
printer.error(&#34;--edit requires name and at least one regex&#34;)
return
self.service.update_context(args.edit[0], args.edit[1:])
printer.success(f&#34;Context &#39;{args.edit[0]}&#39; modified successfully.&#34;)
else:
# Default behavior if no flags: show list
self.dispatch_ls(args)
except ValueError as e:
printer.error(str(e))
except ConnpyError as e:
printer.error(str(e))
sys.exit(1)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.context_handler.ContextHandler.dispatch_ls"><code class="name flex">
<span>def <span class="ident">dispatch_ls</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def dispatch_ls(self, args):
contexts = self.service.list_contexts()
for ctx in contexts:
if ctx[&#34;active&#34;]:
printer.success(f&#34;{ctx[&#39;name&#39;]} (active)&#34;)
else:
printer.custom(&#34; &#34;, ctx[&#34;name&#34;])</code></pre>
</details>
<div class="desc"></div>
</dd>
</dl>
</dd>
</dl>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="connpy.cli" href="index.html">connpy.cli</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="connpy.cli.context_handler.ContextHandler" href="#connpy.cli.context_handler.ContextHandler">ContextHandler</a></code></h4>
<ul class="">
<li><code><a title="connpy.cli.context_handler.ContextHandler.dispatch" href="#connpy.cli.context_handler.ContextHandler.dispatch">dispatch</a></code></li>
<li><code><a title="connpy.cli.context_handler.ContextHandler.dispatch_ls" href="#connpy.cli.context_handler.ContextHandler.dispatch_ls">dispatch_ls</a></code></li>
</ul>
</li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.5</a>.</p>
</footer>
</body>
</html>
+523
View File
@@ -0,0 +1,523 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.5">
<title>connpy.cli.forms API documentation</title>
<meta name="description" content="">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source > summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible;min-width:max-content}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin:1em 0}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
/* Collapse source docstrings */
setTimeout(() => {
[...document.querySelectorAll('.hljs.language-python > .hljs-string')]
.filter(el => el.innerHTML.length > 200 && ['"""', "'''"].includes(el.innerHTML.substring(0, 3)))
.forEach(el => {
let d = document.createElement('details');
d.classList.add('hljs-string');
d.innerHTML = '<summary>"""</summary>' + el.innerHTML.substring(3);
el.replaceWith(d);
});
}, 100);
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>connpy.cli.forms</code></h1>
</header>
<section id="section-intro">
</section>
<section>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="connpy.cli.forms.Forms"><code class="flex name class">
<span>class <span class="ident">Forms</span></span>
<span>(</span><span>app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class Forms:
def __init__(self, app):
self.app = app
self.validators = Validators(app)
def questions_edit(self):
questions = []
questions.append(inquirer.Confirm(&#34;host&#34;, message=&#34;Edit Hostname/IP?&#34;))
questions.append(inquirer.Confirm(&#34;protocol&#34;, message=&#34;Edit Protocol/app?&#34;))
questions.append(inquirer.Confirm(&#34;port&#34;, message=&#34;Edit Port?&#34;))
questions.append(inquirer.Confirm(&#34;options&#34;, message=&#34;Edit Options?&#34;))
questions.append(inquirer.Confirm(&#34;logs&#34;, message=&#34;Edit logging path/file?&#34;))
questions.append(inquirer.Confirm(&#34;tags&#34;, message=&#34;Edit tags?&#34;))
questions.append(inquirer.Confirm(&#34;jumphost&#34;, message=&#34;Edit jumphost?&#34;))
questions.append(inquirer.Confirm(&#34;user&#34;, message=&#34;Edit User?&#34;))
questions.append(inquirer.Confirm(&#34;password&#34;, message=&#34;Edit password?&#34;))
return inquirer.prompt(questions)
def questions_nodes(self, unique, uniques=None, edit=None):
try:
defaults = self.app.services.nodes.get_node_details(unique)
if &#34;tags&#34; not in defaults:
defaults[&#34;tags&#34;] = &#34;&#34;
if &#34;jumphost&#34; not in defaults:
defaults[&#34;jumphost&#34;] = &#34;&#34;
except Exception:
defaults = {&#34;host&#34;: &#34;&#34;, &#34;protocol&#34;: &#34;&#34;, &#34;port&#34;: &#34;&#34;, &#34;user&#34;: &#34;&#34;, &#34;options&#34;: &#34;&#34;, &#34;logs&#34;: &#34;&#34;, &#34;tags&#34;: &#34;&#34;, &#34;password&#34;: &#34;&#34;, &#34;jumphost&#34;: &#34;&#34;}
node = {}
if edit is None:
edit = {&#34;host&#34;: True, &#34;protocol&#34;: True, &#34;port&#34;: True, &#34;user&#34;: True, &#34;password&#34;: True, &#34;options&#34;: True, &#34;logs&#34;: True, &#34;tags&#34;: True, &#34;jumphost&#34;: True}
questions = []
if edit[&#34;host&#34;]:
questions.append(inquirer.Text(&#34;host&#34;, message=&#34;Add Hostname or IP&#34;, validate=self.validators.host_validation, default=defaults[&#34;host&#34;]))
else:
node[&#34;host&#34;] = defaults[&#34;host&#34;]
if edit[&#34;protocol&#34;]:
questions.append(inquirer.Text(&#34;protocol&#34;, message=&#34;Select Protocol/app&#34;, validate=self.validators.protocol_validation, default=defaults[&#34;protocol&#34;]))
else:
node[&#34;protocol&#34;] = defaults[&#34;protocol&#34;]
if edit[&#34;port&#34;]:
questions.append(inquirer.Text(&#34;port&#34;, message=&#34;Select Port Number&#34;, validate=self.validators.port_validation, default=defaults[&#34;port&#34;]))
else:
node[&#34;port&#34;] = defaults[&#34;port&#34;]
if edit[&#34;options&#34;]:
questions.append(inquirer.Text(&#34;options&#34;, message=&#34;Pass extra options to protocol/app&#34;, validate=self.validators.default_validation, default=defaults[&#34;options&#34;]))
else:
node[&#34;options&#34;] = defaults[&#34;options&#34;]
if edit[&#34;logs&#34;]:
questions.append(inquirer.Text(&#34;logs&#34;, message=&#34;Pick logging path/file &#34;, validate=self.validators.default_validation, default=defaults[&#34;logs&#34;].replace(&#34;{&#34;, &#34;{{&#34;).replace(&#34;}&#34;, &#34;}}&#34;)))
else:
node[&#34;logs&#34;] = defaults[&#34;logs&#34;]
if edit[&#34;tags&#34;]:
questions.append(inquirer.Text(&#34;tags&#34;, message=&#34;Add tags dictionary&#34;, validate=self.validators.tags_validation, default=str(defaults[&#34;tags&#34;]).replace(&#34;{&#34;, &#34;{{&#34;).replace(&#34;}&#34;, &#34;}}&#34;)))
else:
node[&#34;tags&#34;] = defaults[&#34;tags&#34;]
if edit[&#34;jumphost&#34;]:
questions.append(inquirer.Text(&#34;jumphost&#34;, message=&#34;Add Jumphost node&#34;, validate=self.validators.jumphost_validation, default=str(defaults[&#34;jumphost&#34;]).replace(&#34;{&#34;, &#34;{{&#34;).replace(&#34;}&#34;, &#34;}}&#34;)))
else:
node[&#34;jumphost&#34;] = defaults[&#34;jumphost&#34;]
if edit[&#34;user&#34;]:
questions.append(inquirer.Text(&#34;user&#34;, message=&#34;Pick username&#34;, validate=self.validators.default_validation, default=defaults[&#34;user&#34;]))
else:
node[&#34;user&#34;] = defaults[&#34;user&#34;]
if edit[&#34;password&#34;]:
questions.append(inquirer.List(&#34;password&#34;, message=&#34;Password: Use a local password, no password or a list of profiles to reference?&#34;, choices=[&#34;Local Password&#34;, &#34;Profiles&#34;, &#34;No Password&#34;]))
else:
node[&#34;password&#34;] = defaults[&#34;password&#34;]
answer = inquirer.prompt(questions)
if answer is None:
return False
if &#34;password&#34; in answer:
if answer[&#34;password&#34;] == &#34;Local Password&#34;:
passq = [inquirer.Password(&#34;password&#34;, message=&#34;Set Password&#34;)]
passa = inquirer.prompt(passq)
if passa is None:
return False
answer[&#34;password&#34;] = self.app.services.config_svc.encrypt_password(passa[&#34;password&#34;])
elif answer[&#34;password&#34;] == &#34;Profiles&#34;:
passq = [(inquirer.Text(&#34;password&#34;, message=&#34;Set a @profile or a comma separated list of @profiles&#34;, validate=self.validators.pass_validation))]
passa = inquirer.prompt(passq)
if passa is None:
return False
answer[&#34;password&#34;] = passa[&#34;password&#34;].split(&#34;,&#34;)
elif answer[&#34;password&#34;] == &#34;No Password&#34;:
answer[&#34;password&#34;] = &#34;&#34;
if &#34;tags&#34; in answer and not answer[&#34;tags&#34;].startswith(&#34;@&#34;) and answer[&#34;tags&#34;]:
answer[&#34;tags&#34;] = ast.literal_eval(answer[&#34;tags&#34;])
result = {**uniques, **answer, **node}
result[&#34;type&#34;] = &#34;connection&#34;
return result
def questions_profiles(self, unique, edit=None):
try:
defaults = self.app.services.profiles.get_profile(unique, resolve=False)
if &#34;tags&#34; not in defaults:
defaults[&#34;tags&#34;] = &#34;&#34;
if &#34;jumphost&#34; not in defaults:
defaults[&#34;jumphost&#34;] = &#34;&#34;
except Exception:
defaults = {&#34;host&#34;: &#34;&#34;, &#34;protocol&#34;: &#34;&#34;, &#34;port&#34;: &#34;&#34;, &#34;user&#34;: &#34;&#34;, &#34;options&#34;: &#34;&#34;, &#34;logs&#34;: &#34;&#34;, &#34;tags&#34;: &#34;&#34;, &#34;jumphost&#34;: &#34;&#34;}
profile = {}
if edit is None:
edit = {&#34;host&#34;: True, &#34;protocol&#34;: True, &#34;port&#34;: True, &#34;user&#34;: True, &#34;password&#34;: True, &#34;options&#34;: True, &#34;logs&#34;: True, &#34;tags&#34;: True, &#34;jumphost&#34;: True}
questions = []
if edit[&#34;host&#34;]:
questions.append(inquirer.Text(&#34;host&#34;, message=&#34;Add Hostname or IP&#34;, default=defaults[&#34;host&#34;]))
else:
profile[&#34;host&#34;] = defaults[&#34;host&#34;]
if edit[&#34;protocol&#34;]:
questions.append(inquirer.Text(&#34;protocol&#34;, message=&#34;Select Protocol/app&#34;, validate=self.validators.profile_protocol_validation, default=defaults[&#34;protocol&#34;]))
else:
profile[&#34;protocol&#34;] = defaults[&#34;protocol&#34;]
if edit[&#34;port&#34;]:
questions.append(inquirer.Text(&#34;port&#34;, message=&#34;Select Port Number&#34;, validate=self.validators.profile_port_validation, default=defaults[&#34;port&#34;]))
else:
profile[&#34;port&#34;] = defaults[&#34;port&#34;]
if edit[&#34;options&#34;]:
questions.append(inquirer.Text(&#34;options&#34;, message=&#34;Pass extra options to protocol/app&#34;, default=defaults[&#34;options&#34;]))
else:
profile[&#34;options&#34;] = defaults[&#34;options&#34;]
if edit[&#34;logs&#34;]:
questions.append(inquirer.Text(&#34;logs&#34;, message=&#34;Pick logging path/file &#34;, default=defaults[&#34;logs&#34;].replace(&#34;{&#34;, &#34;{{&#34;).replace(&#34;}&#34;, &#34;}}&#34;)))
else:
profile[&#34;logs&#34;] = defaults[&#34;logs&#34;]
if edit[&#34;tags&#34;]:
questions.append(inquirer.Text(&#34;tags&#34;, message=&#34;Add tags dictionary&#34;, validate=self.validators.profile_tags_validation, default=str(defaults[&#34;tags&#34;]).replace(&#34;{&#34;, &#34;{{&#34;).replace(&#34;}&#34;, &#34;}}&#34;)))
else:
profile[&#34;tags&#34;] = defaults[&#34;tags&#34;]
if edit[&#34;jumphost&#34;]:
questions.append(inquirer.Text(&#34;jumphost&#34;, message=&#34;Add Jumphost node&#34;, validate=self.validators.profile_jumphost_validation, default=str(defaults[&#34;jumphost&#34;]).replace(&#34;{&#34;, &#34;{{&#34;).replace(&#34;}&#34;, &#34;}}&#34;)))
else:
profile[&#34;jumphost&#34;] = defaults[&#34;jumphost&#34;]
if edit[&#34;user&#34;]:
questions.append(inquirer.Text(&#34;user&#34;, message=&#34;Pick username&#34;, default=defaults[&#34;user&#34;]))
else:
profile[&#34;user&#34;] = defaults[&#34;user&#34;]
if edit[&#34;password&#34;]:
questions.append(inquirer.Password(&#34;password&#34;, message=&#34;Set Password&#34;))
else:
profile[&#34;password&#34;] = defaults[&#34;password&#34;]
answer = inquirer.prompt(questions)
if answer is None:
return False
if &#34;password&#34; in answer:
if answer[&#34;password&#34;] != &#34;&#34;:
answer[&#34;password&#34;] = self.app.services.config_svc.encrypt_password(answer[&#34;password&#34;])
if &#34;tags&#34; in answer and answer[&#34;tags&#34;]:
answer[&#34;tags&#34;] = ast.literal_eval(answer[&#34;tags&#34;])
result = {**answer, **profile}
result[&#34;id&#34;] = unique
return result
def questions_bulk(self, nodes=&#34;&#34;, hosts=&#34;&#34;):
questions = []
questions.append(inquirer.Text(&#34;ids&#34;, message=&#34;add a comma separated list of nodes to add&#34;, default=nodes, validate=self.validators.bulk_node_validation))
questions.append(inquirer.Text(&#34;location&#34;, message=&#34;Add a @folder, @subfolder@folder or leave empty&#34;, validate=self.validators.bulk_folder_validation))
questions.append(inquirer.Text(&#34;host&#34;, message=&#34;Add comma separated list of Hostnames or IPs&#34;, default=hosts, validate=self.validators.bulk_host_validation))
questions.append(inquirer.Text(&#34;protocol&#34;, message=&#34;Select Protocol/app&#34;, validate=self.validators.protocol_validation))
questions.append(inquirer.Text(&#34;port&#34;, message=&#34;Select Port Number&#34;, validate=self.validators.port_validation))
questions.append(inquirer.Text(&#34;options&#34;, message=&#34;Pass extra options to protocol/app&#34;, validate=self.validators.default_validation))
questions.append(inquirer.Text(&#34;logs&#34;, message=&#34;Pick logging path/file &#34;, validate=self.validators.default_validation))
questions.append(inquirer.Text(&#34;tags&#34;, message=&#34;Add tags dictionary&#34;, validate=self.validators.tags_validation))
questions.append(inquirer.Text(&#34;jumphost&#34;, message=&#34;Add Jumphost node&#34;, validate=self.validators.jumphost_validation))
questions.append(inquirer.Text(&#34;user&#34;, message=&#34;Pick username&#34;, validate=self.validators.default_validation))
questions.append(inquirer.List(&#34;password&#34;, message=&#34;Password: Use a local password, no password or a list of profiles to reference?&#34;, choices=[&#34;Local Password&#34;, &#34;Profiles&#34;, &#34;No Password&#34;]))
answer = inquirer.prompt(questions)
if answer is None:
return False
if &#34;password&#34; in answer:
if answer[&#34;password&#34;] == &#34;Local Password&#34;:
passq = [inquirer.Password(&#34;password&#34;, message=&#34;Set Password&#34;)]
passa = inquirer.prompt(passq)
answer[&#34;password&#34;] = self.app.services.config_svc.encrypt_password(passa[&#34;password&#34;])
elif answer[&#34;password&#34;] == &#34;Profiles&#34;:
passq = [(inquirer.Text(&#34;password&#34;, message=&#34;Set a @profile or a comma separated list of @profiles&#34;, validate=self.validators.pass_validation))]
passa = inquirer.prompt(passq)
answer[&#34;password&#34;] = passa[&#34;password&#34;].split(&#34;,&#34;)
elif answer[&#34;password&#34;] == &#34;No Password&#34;:
answer[&#34;password&#34;] = &#34;&#34;
answer[&#34;type&#34;] = &#34;connection&#34;
if &#34;tags&#34; in answer and not answer[&#34;tags&#34;].startswith(&#34;@&#34;) and answer[&#34;tags&#34;]:
answer[&#34;tags&#34;] = ast.literal_eval(answer[&#34;tags&#34;])
return answer</code></pre>
</details>
<div class="desc"></div>
<h3>Methods</h3>
<dl>
<dt id="connpy.cli.forms.Forms.questions_bulk"><code class="name flex">
<span>def <span class="ident">questions_bulk</span></span>(<span>self, nodes='', hosts='')</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def questions_bulk(self, nodes=&#34;&#34;, hosts=&#34;&#34;):
questions = []
questions.append(inquirer.Text(&#34;ids&#34;, message=&#34;add a comma separated list of nodes to add&#34;, default=nodes, validate=self.validators.bulk_node_validation))
questions.append(inquirer.Text(&#34;location&#34;, message=&#34;Add a @folder, @subfolder@folder or leave empty&#34;, validate=self.validators.bulk_folder_validation))
questions.append(inquirer.Text(&#34;host&#34;, message=&#34;Add comma separated list of Hostnames or IPs&#34;, default=hosts, validate=self.validators.bulk_host_validation))
questions.append(inquirer.Text(&#34;protocol&#34;, message=&#34;Select Protocol/app&#34;, validate=self.validators.protocol_validation))
questions.append(inquirer.Text(&#34;port&#34;, message=&#34;Select Port Number&#34;, validate=self.validators.port_validation))
questions.append(inquirer.Text(&#34;options&#34;, message=&#34;Pass extra options to protocol/app&#34;, validate=self.validators.default_validation))
questions.append(inquirer.Text(&#34;logs&#34;, message=&#34;Pick logging path/file &#34;, validate=self.validators.default_validation))
questions.append(inquirer.Text(&#34;tags&#34;, message=&#34;Add tags dictionary&#34;, validate=self.validators.tags_validation))
questions.append(inquirer.Text(&#34;jumphost&#34;, message=&#34;Add Jumphost node&#34;, validate=self.validators.jumphost_validation))
questions.append(inquirer.Text(&#34;user&#34;, message=&#34;Pick username&#34;, validate=self.validators.default_validation))
questions.append(inquirer.List(&#34;password&#34;, message=&#34;Password: Use a local password, no password or a list of profiles to reference?&#34;, choices=[&#34;Local Password&#34;, &#34;Profiles&#34;, &#34;No Password&#34;]))
answer = inquirer.prompt(questions)
if answer is None:
return False
if &#34;password&#34; in answer:
if answer[&#34;password&#34;] == &#34;Local Password&#34;:
passq = [inquirer.Password(&#34;password&#34;, message=&#34;Set Password&#34;)]
passa = inquirer.prompt(passq)
answer[&#34;password&#34;] = self.app.services.config_svc.encrypt_password(passa[&#34;password&#34;])
elif answer[&#34;password&#34;] == &#34;Profiles&#34;:
passq = [(inquirer.Text(&#34;password&#34;, message=&#34;Set a @profile or a comma separated list of @profiles&#34;, validate=self.validators.pass_validation))]
passa = inquirer.prompt(passq)
answer[&#34;password&#34;] = passa[&#34;password&#34;].split(&#34;,&#34;)
elif answer[&#34;password&#34;] == &#34;No Password&#34;:
answer[&#34;password&#34;] = &#34;&#34;
answer[&#34;type&#34;] = &#34;connection&#34;
if &#34;tags&#34; in answer and not answer[&#34;tags&#34;].startswith(&#34;@&#34;) and answer[&#34;tags&#34;]:
answer[&#34;tags&#34;] = ast.literal_eval(answer[&#34;tags&#34;])
return answer</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.forms.Forms.questions_edit"><code class="name flex">
<span>def <span class="ident">questions_edit</span></span>(<span>self)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def questions_edit(self):
questions = []
questions.append(inquirer.Confirm(&#34;host&#34;, message=&#34;Edit Hostname/IP?&#34;))
questions.append(inquirer.Confirm(&#34;protocol&#34;, message=&#34;Edit Protocol/app?&#34;))
questions.append(inquirer.Confirm(&#34;port&#34;, message=&#34;Edit Port?&#34;))
questions.append(inquirer.Confirm(&#34;options&#34;, message=&#34;Edit Options?&#34;))
questions.append(inquirer.Confirm(&#34;logs&#34;, message=&#34;Edit logging path/file?&#34;))
questions.append(inquirer.Confirm(&#34;tags&#34;, message=&#34;Edit tags?&#34;))
questions.append(inquirer.Confirm(&#34;jumphost&#34;, message=&#34;Edit jumphost?&#34;))
questions.append(inquirer.Confirm(&#34;user&#34;, message=&#34;Edit User?&#34;))
questions.append(inquirer.Confirm(&#34;password&#34;, message=&#34;Edit password?&#34;))
return inquirer.prompt(questions)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.forms.Forms.questions_nodes"><code class="name flex">
<span>def <span class="ident">questions_nodes</span></span>(<span>self, unique, uniques=None, edit=None)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def questions_nodes(self, unique, uniques=None, edit=None):
try:
defaults = self.app.services.nodes.get_node_details(unique)
if &#34;tags&#34; not in defaults:
defaults[&#34;tags&#34;] = &#34;&#34;
if &#34;jumphost&#34; not in defaults:
defaults[&#34;jumphost&#34;] = &#34;&#34;
except Exception:
defaults = {&#34;host&#34;: &#34;&#34;, &#34;protocol&#34;: &#34;&#34;, &#34;port&#34;: &#34;&#34;, &#34;user&#34;: &#34;&#34;, &#34;options&#34;: &#34;&#34;, &#34;logs&#34;: &#34;&#34;, &#34;tags&#34;: &#34;&#34;, &#34;password&#34;: &#34;&#34;, &#34;jumphost&#34;: &#34;&#34;}
node = {}
if edit is None:
edit = {&#34;host&#34;: True, &#34;protocol&#34;: True, &#34;port&#34;: True, &#34;user&#34;: True, &#34;password&#34;: True, &#34;options&#34;: True, &#34;logs&#34;: True, &#34;tags&#34;: True, &#34;jumphost&#34;: True}
questions = []
if edit[&#34;host&#34;]:
questions.append(inquirer.Text(&#34;host&#34;, message=&#34;Add Hostname or IP&#34;, validate=self.validators.host_validation, default=defaults[&#34;host&#34;]))
else:
node[&#34;host&#34;] = defaults[&#34;host&#34;]
if edit[&#34;protocol&#34;]:
questions.append(inquirer.Text(&#34;protocol&#34;, message=&#34;Select Protocol/app&#34;, validate=self.validators.protocol_validation, default=defaults[&#34;protocol&#34;]))
else:
node[&#34;protocol&#34;] = defaults[&#34;protocol&#34;]
if edit[&#34;port&#34;]:
questions.append(inquirer.Text(&#34;port&#34;, message=&#34;Select Port Number&#34;, validate=self.validators.port_validation, default=defaults[&#34;port&#34;]))
else:
node[&#34;port&#34;] = defaults[&#34;port&#34;]
if edit[&#34;options&#34;]:
questions.append(inquirer.Text(&#34;options&#34;, message=&#34;Pass extra options to protocol/app&#34;, validate=self.validators.default_validation, default=defaults[&#34;options&#34;]))
else:
node[&#34;options&#34;] = defaults[&#34;options&#34;]
if edit[&#34;logs&#34;]:
questions.append(inquirer.Text(&#34;logs&#34;, message=&#34;Pick logging path/file &#34;, validate=self.validators.default_validation, default=defaults[&#34;logs&#34;].replace(&#34;{&#34;, &#34;{{&#34;).replace(&#34;}&#34;, &#34;}}&#34;)))
else:
node[&#34;logs&#34;] = defaults[&#34;logs&#34;]
if edit[&#34;tags&#34;]:
questions.append(inquirer.Text(&#34;tags&#34;, message=&#34;Add tags dictionary&#34;, validate=self.validators.tags_validation, default=str(defaults[&#34;tags&#34;]).replace(&#34;{&#34;, &#34;{{&#34;).replace(&#34;}&#34;, &#34;}}&#34;)))
else:
node[&#34;tags&#34;] = defaults[&#34;tags&#34;]
if edit[&#34;jumphost&#34;]:
questions.append(inquirer.Text(&#34;jumphost&#34;, message=&#34;Add Jumphost node&#34;, validate=self.validators.jumphost_validation, default=str(defaults[&#34;jumphost&#34;]).replace(&#34;{&#34;, &#34;{{&#34;).replace(&#34;}&#34;, &#34;}}&#34;)))
else:
node[&#34;jumphost&#34;] = defaults[&#34;jumphost&#34;]
if edit[&#34;user&#34;]:
questions.append(inquirer.Text(&#34;user&#34;, message=&#34;Pick username&#34;, validate=self.validators.default_validation, default=defaults[&#34;user&#34;]))
else:
node[&#34;user&#34;] = defaults[&#34;user&#34;]
if edit[&#34;password&#34;]:
questions.append(inquirer.List(&#34;password&#34;, message=&#34;Password: Use a local password, no password or a list of profiles to reference?&#34;, choices=[&#34;Local Password&#34;, &#34;Profiles&#34;, &#34;No Password&#34;]))
else:
node[&#34;password&#34;] = defaults[&#34;password&#34;]
answer = inquirer.prompt(questions)
if answer is None:
return False
if &#34;password&#34; in answer:
if answer[&#34;password&#34;] == &#34;Local Password&#34;:
passq = [inquirer.Password(&#34;password&#34;, message=&#34;Set Password&#34;)]
passa = inquirer.prompt(passq)
if passa is None:
return False
answer[&#34;password&#34;] = self.app.services.config_svc.encrypt_password(passa[&#34;password&#34;])
elif answer[&#34;password&#34;] == &#34;Profiles&#34;:
passq = [(inquirer.Text(&#34;password&#34;, message=&#34;Set a @profile or a comma separated list of @profiles&#34;, validate=self.validators.pass_validation))]
passa = inquirer.prompt(passq)
if passa is None:
return False
answer[&#34;password&#34;] = passa[&#34;password&#34;].split(&#34;,&#34;)
elif answer[&#34;password&#34;] == &#34;No Password&#34;:
answer[&#34;password&#34;] = &#34;&#34;
if &#34;tags&#34; in answer and not answer[&#34;tags&#34;].startswith(&#34;@&#34;) and answer[&#34;tags&#34;]:
answer[&#34;tags&#34;] = ast.literal_eval(answer[&#34;tags&#34;])
result = {**uniques, **answer, **node}
result[&#34;type&#34;] = &#34;connection&#34;
return result</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.forms.Forms.questions_profiles"><code class="name flex">
<span>def <span class="ident">questions_profiles</span></span>(<span>self, unique, edit=None)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def questions_profiles(self, unique, edit=None):
try:
defaults = self.app.services.profiles.get_profile(unique, resolve=False)
if &#34;tags&#34; not in defaults:
defaults[&#34;tags&#34;] = &#34;&#34;
if &#34;jumphost&#34; not in defaults:
defaults[&#34;jumphost&#34;] = &#34;&#34;
except Exception:
defaults = {&#34;host&#34;: &#34;&#34;, &#34;protocol&#34;: &#34;&#34;, &#34;port&#34;: &#34;&#34;, &#34;user&#34;: &#34;&#34;, &#34;options&#34;: &#34;&#34;, &#34;logs&#34;: &#34;&#34;, &#34;tags&#34;: &#34;&#34;, &#34;jumphost&#34;: &#34;&#34;}
profile = {}
if edit is None:
edit = {&#34;host&#34;: True, &#34;protocol&#34;: True, &#34;port&#34;: True, &#34;user&#34;: True, &#34;password&#34;: True, &#34;options&#34;: True, &#34;logs&#34;: True, &#34;tags&#34;: True, &#34;jumphost&#34;: True}
questions = []
if edit[&#34;host&#34;]:
questions.append(inquirer.Text(&#34;host&#34;, message=&#34;Add Hostname or IP&#34;, default=defaults[&#34;host&#34;]))
else:
profile[&#34;host&#34;] = defaults[&#34;host&#34;]
if edit[&#34;protocol&#34;]:
questions.append(inquirer.Text(&#34;protocol&#34;, message=&#34;Select Protocol/app&#34;, validate=self.validators.profile_protocol_validation, default=defaults[&#34;protocol&#34;]))
else:
profile[&#34;protocol&#34;] = defaults[&#34;protocol&#34;]
if edit[&#34;port&#34;]:
questions.append(inquirer.Text(&#34;port&#34;, message=&#34;Select Port Number&#34;, validate=self.validators.profile_port_validation, default=defaults[&#34;port&#34;]))
else:
profile[&#34;port&#34;] = defaults[&#34;port&#34;]
if edit[&#34;options&#34;]:
questions.append(inquirer.Text(&#34;options&#34;, message=&#34;Pass extra options to protocol/app&#34;, default=defaults[&#34;options&#34;]))
else:
profile[&#34;options&#34;] = defaults[&#34;options&#34;]
if edit[&#34;logs&#34;]:
questions.append(inquirer.Text(&#34;logs&#34;, message=&#34;Pick logging path/file &#34;, default=defaults[&#34;logs&#34;].replace(&#34;{&#34;, &#34;{{&#34;).replace(&#34;}&#34;, &#34;}}&#34;)))
else:
profile[&#34;logs&#34;] = defaults[&#34;logs&#34;]
if edit[&#34;tags&#34;]:
questions.append(inquirer.Text(&#34;tags&#34;, message=&#34;Add tags dictionary&#34;, validate=self.validators.profile_tags_validation, default=str(defaults[&#34;tags&#34;]).replace(&#34;{&#34;, &#34;{{&#34;).replace(&#34;}&#34;, &#34;}}&#34;)))
else:
profile[&#34;tags&#34;] = defaults[&#34;tags&#34;]
if edit[&#34;jumphost&#34;]:
questions.append(inquirer.Text(&#34;jumphost&#34;, message=&#34;Add Jumphost node&#34;, validate=self.validators.profile_jumphost_validation, default=str(defaults[&#34;jumphost&#34;]).replace(&#34;{&#34;, &#34;{{&#34;).replace(&#34;}&#34;, &#34;}}&#34;)))
else:
profile[&#34;jumphost&#34;] = defaults[&#34;jumphost&#34;]
if edit[&#34;user&#34;]:
questions.append(inquirer.Text(&#34;user&#34;, message=&#34;Pick username&#34;, default=defaults[&#34;user&#34;]))
else:
profile[&#34;user&#34;] = defaults[&#34;user&#34;]
if edit[&#34;password&#34;]:
questions.append(inquirer.Password(&#34;password&#34;, message=&#34;Set Password&#34;))
else:
profile[&#34;password&#34;] = defaults[&#34;password&#34;]
answer = inquirer.prompt(questions)
if answer is None:
return False
if &#34;password&#34; in answer:
if answer[&#34;password&#34;] != &#34;&#34;:
answer[&#34;password&#34;] = self.app.services.config_svc.encrypt_password(answer[&#34;password&#34;])
if &#34;tags&#34; in answer and answer[&#34;tags&#34;]:
answer[&#34;tags&#34;] = ast.literal_eval(answer[&#34;tags&#34;])
result = {**answer, **profile}
result[&#34;id&#34;] = unique
return result</code></pre>
</details>
<div class="desc"></div>
</dd>
</dl>
</dd>
</dl>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="connpy.cli" href="index.html">connpy.cli</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="connpy.cli.forms.Forms" href="#connpy.cli.forms.Forms">Forms</a></code></h4>
<ul class="">
<li><code><a title="connpy.cli.forms.Forms.questions_bulk" href="#connpy.cli.forms.Forms.questions_bulk">questions_bulk</a></code></li>
<li><code><a title="connpy.cli.forms.Forms.questions_edit" href="#connpy.cli.forms.Forms.questions_edit">questions_edit</a></code></li>
<li><code><a title="connpy.cli.forms.Forms.questions_nodes" href="#connpy.cli.forms.Forms.questions_nodes">questions_nodes</a></code></li>
<li><code><a title="connpy.cli.forms.Forms.questions_profiles" href="#connpy.cli.forms.Forms.questions_profiles">questions_profiles</a></code></li>
</ul>
</li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.5</a>.</p>
</footer>
</body>
</html>
+309
View File
@@ -0,0 +1,309 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.5">
<title>connpy.cli.help_text API documentation</title>
<meta name="description" content="">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source > summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible;min-width:max-content}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin:1em 0}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
/* Collapse source docstrings */
setTimeout(() => {
[...document.querySelectorAll('.hljs.language-python > .hljs-string')]
.filter(el => el.innerHTML.length > 200 && ['"""', "'''"].includes(el.innerHTML.substring(0, 3)))
.forEach(el => {
let d = document.createElement('details');
d.classList.add('hljs-string');
d.innerHTML = '<summary>"""</summary>' + el.innerHTML.substring(3);
el.replaceWith(d);
});
}, 100);
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>connpy.cli.help_text</code></h1>
</header>
<section id="section-intro">
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-functions">Functions</h2>
<dl>
<dt id="connpy.cli.help_text.get_help"><code class="name flex">
<span>def <span class="ident">get_help</span></span>(<span>type, parsers=None)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def get_help(type, parsers=None):
if type == &#34;export&#34;:
return &#34;Export /path/to/file.yml \[@subfolder1]\[@folder1] \[@subfolderN]\[@folderN]&#34;
if type == &#34;import&#34;:
return &#34;Import /path/to/file.yml&#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;
if type == &#34;usage&#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;connpy [-h] [--add | --del | --mod | --show | --debug] [node|folder] [--sftp]\n connpy {{{commands}}} ...&#34;
return usage_help
return get_instructions(type)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.help_text.get_instructions"><code class="name flex">
<span>def <span class="ident">get_instructions</span></span>(<span>type='add')</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def get_instructions(type=&#34;add&#34;):
if type == &#34;add&#34;:
return &#34;&#34;&#34;
Welcome to Connpy node Addition Wizard!
Here are some important instructions and tips for configuring your new node:
1. **Profiles**:
- You can use the configured settings in a profile using `@profilename`.
2. **Available Protocols and Apps**:
- ssh
- telnet
- kubectl (`kubectl exec`)
- docker (`docker exec`)
3. **Optional Values**:
- You can leave any value empty except for the hostname/IP.
4. **Passwords**:
- You can pass one or more passwords using comma-separated `@profiles`.
5. **Logging**:
- You can use the following variables in the logging file name:
- `${id}`
- `${unique}`
- `${host}`
- `${port}`
- `${user}`
- `${protocol}`
6. **Well-Known Tags**:
- `os`: Identified by AI to generate commands based on the operating system.
- `screen_length_command`: Used by automation to avoid pagination on different devices (e.g., `terminal length 0` for Cisco devices).
- `prompt`: Replaces default app prompt to identify the end of output or where the user can start inputting commands.
- `kube_command`: Replaces the default command (`/bin/bash`) for `kubectl exec`.
- `docker_command`: Replaces the default command for `docker exec`.
&#34;&#34;&#34;
if type == &#34;bashcompletion&#34;:
return &#39;&#39;&#39;
# Bash completion for connpy
# Run: eval &#34;$(connpy config --completion bash)&#34;
# Or add it to your .bashrc
_connpy_autocomplete()
{
local strings
strings=$(python3 -m connpy.completion bash ${#COMP_WORDS[@]} &#34;${COMP_WORDS[@]}&#34;)
local IFS=$&#39;\\t&#39;
COMPREPLY=( $(compgen -W &#34;$strings&#34; -- &#34;${COMP_WORDS[$COMP_CWORD]}&#34;) )
}
complete -o nosort -F _connpy_autocomplete conn
complete -o nosort -F _connpy_autocomplete connpy
&#39;&#39;&#39;
if type == &#34;zshcompletion&#34;:
return &#39;&#39;&#39;
# Zsh completion for connpy
# Run: eval &#34;$(connpy config --completion zsh)&#34;
# Or add it to your .zshrc
# Make sure compinit is loaded
autoload -U compinit &amp;&amp; compinit
_connpy_autocomplete()
{
local COMP_WORDS num strings
COMP_WORDS=( $words )
num=${#COMP_WORDS[@]}
if [[ $words =~ &#39;.* $&#39; ]]; then
num=$(($num + 1))
fi
strings=$(python3 -m connpy.completion zsh ${num} ${COMP_WORDS[@]})
local IFS=$&#39;\\t&#39;
compadd &#34;$@&#34; -- ${=strings}
}
compdef _connpy_autocomplete conn
compdef _connpy_autocomplete connpy
&#39;&#39;&#39;
if type == &#34;fzf_wrapper_bash&#34;:
return &#39;&#39;&#39;\n#Here starts bash 0ms fzf wrapper for connpy
connpy() {
if [ $# -eq 0 ]; then
local selected
local configdir=$(cat ~/.config/conn/.folder 2&gt;/dev/null || echo ~/.config/conn)
if [ -s &#34;$configdir/.fzf_nodes_cache.txt&#34; ]; then
selected=$(cat &#34;$configdir/.fzf_nodes_cache.txt&#34; | fzf-tmux -i -d 25%)
else
command connpy
return
fi
if [ -n &#34;$selected&#34; ]; then
command connpy &#34;$selected&#34;
fi
else
command connpy &#34;$@&#34;
fi
}
alias c=&#34;connpy&#34;
#Here ends bash 0ms fzf wrapper for connpy
&#39;&#39;&#39;
if type == &#34;fzf_wrapper_zsh&#34;:
return &#39;&#39;&#39;\n#Here starts zsh 0ms fzf wrapper for connpy
connpy() {
if [ $# -eq 0 ]; then
local selected
local configdir=$(cat ~/.config/conn/.folder 2&gt;/dev/null || echo ~/.config/conn)
if [ -s &#34;$configdir/.fzf_nodes_cache.txt&#34; ]; then
selected=$(cat &#34;$configdir/.fzf_nodes_cache.txt&#34; | fzf-tmux -i -d 25%)
else
command connpy
return
fi
if [ -n &#34;$selected&#34; ]; then
command connpy &#34;$selected&#34;
fi
else
command connpy &#34;$@&#34;
fi
}
alias c=&#34;connpy&#34;
#Here ends zsh 0ms fzf wrapper for connpy
&#39;&#39;&#39;
if type == &#34;run&#34;:
return &#34;node[@subfolder][@folder] commmand to run\nRun the specific command on the node and print output\n/path/to/file.yaml\nUse a yaml file to run an automation script&#34;
if type == &#34;generate&#34;:
return r&#39;&#39;&#39;---
tasks:
- name: &#34;Config&#34;
action: &#39;run&#39; #Action can be test or run. Mandatory
nodes: #List of nodes to work on. Mandatory
- &#39;router1@office&#39; #You can add specific nodes
- &#39;@aws&#39; #entire folders or subfolders
- &#39;@office&#39;: #or filter inside a folder or subfolder
- &#39;router2&#39;
- &#39;router7&#39;
commands: #List of commands to send, use {name} to pass variables
- &#39;term len 0&#39;
- &#39;conf t&#39;
- &#39;interface {if}&#39;
- &#39;ip address 10.100.100.{id} 255.255.255.255&#39;
- &#39;{commit}&#39;
- &#39;end&#39;
variables: #Variables to use on commands and expected. Optional
__global__: #Global variables to use on all nodes, fallback if missing in the node.
commit: &#39;&#39;
if: &#39;loopback100&#39;
router1@office:
id: 1
router2@office:
id: 2
commit: &#39;commit&#39;
router3@office:
id: 3
vrouter1@aws:
id: 4
vrouterN@aws:
id: 5
output: /home/user/logs #Type of output, if null you only get Connection and test result. Choices are: null,stdout,/path/to/folder. Folder path only works on &#39;run&#39; action.
options:
prompt: r&#39;&gt;$|#$|\$$|&gt;.$|#.$|\$.$&#39; #Optional prompt to check on your devices, default should work on most devices.
parallel: 10 #Optional number of nodes to run commands on parallel. Default 10.
timeout: 20 #Optional time to wait in seconds for prompt, expected or EOF. Default 20.
- name: &#34;TestConfig&#34;
action: &#39;test&#39;
nodes:
- &#39;router1@office&#39;
- &#39;@aws&#39;
- &#39;@office&#39;:
- &#39;router2&#39;
- &#39;router7&#39;
commands:
- &#39;ping 10.100.100.{id}&#39;
expected: &#39;!&#39; #Expected text to find when running test action. Mandatory for &#39;test&#39;
variables:
router1@office:
id: 1
router2@office:
id: 2
commit: &#39;commit&#39;
router3@office:
id: 3
vrouter1@aws:
id: 4
vrouterN@aws:
id: 5
output: null
...&#39;&#39;&#39;
return &#34;&#34;</code></pre>
</details>
<div class="desc"></div>
</dd>
</dl>
</section>
<section>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="connpy.cli" href="index.html">connpy.cli</a></code></li>
</ul>
</li>
<li><h3><a href="#header-functions">Functions</a></h3>
<ul class="">
<li><code><a title="connpy.cli.help_text.get_help" href="#connpy.cli.help_text.get_help">get_help</a></code></li>
<li><code><a title="connpy.cli.help_text.get_instructions" href="#connpy.cli.help_text.get_instructions">get_instructions</a></code></li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.5</a>.</p>
</footer>
</body>
</html>
+213
View File
@@ -0,0 +1,213 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.5">
<title>connpy.cli.helpers API documentation</title>
<meta name="description" content="">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source > summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible;min-width:max-content}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin:1em 0}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
/* Collapse source docstrings */
setTimeout(() => {
[...document.querySelectorAll('.hljs.language-python > .hljs-string')]
.filter(el => el.innerHTML.length > 200 && ['"""', "'''"].includes(el.innerHTML.substring(0, 3)))
.forEach(el => {
let d = document.createElement('details');
d.classList.add('hljs-string');
d.innerHTML = '<summary>"""</summary>' + el.innerHTML.substring(3);
el.replaceWith(d);
});
}, 100);
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>connpy.cli.helpers</code></h1>
</header>
<section id="section-intro">
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-functions">Functions</h2>
<dl>
<dt id="connpy.cli.helpers.choose"><code class="name flex">
<span>def <span class="ident">choose</span></span>(<span>app, list_, name, action)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def choose(app, list_, name, action):
# Generates an inquirer list to pick
# Safeguard: Never prompt if running in autocomplete shell
if os.environ.get(&#34;_ARGCOMPLETE&#34;) or os.environ.get(&#34;COMP_LINE&#34;):
return None
if FzfPrompt and app.fzf and os.environ.get(&#34;_ARGCOMPLETE&#34;) is None and os.environ.get(&#34;COMP_LINE&#34;) is None:
fzf_prompt = FzfPrompt(executable_path=&#34;fzf-tmux&#34;)
if not app.case:
fzf_prompt = FzfPrompt(executable_path=&#34;fzf-tmux -i&#34;)
answer = fzf_prompt.prompt(list_, fzf_options=&#34;-d 25%&#34;)
if len(answer) == 0:
return None
else:
return answer[0]
else:
questions = [inquirer.List(name, message=&#34;Pick {} to {}:&#34;.format(name,action), choices=list_, carousel=True)]
answer = inquirer.prompt(questions)
if answer == None:
return None
else:
return answer[name]</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.helpers.folders_completer"><code class="name flex">
<span>def <span class="ident">folders_completer</span></span>(<span>prefix, parsed_args, **kwargs)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def folders_completer(prefix, parsed_args, **kwargs):
configdir = get_config_dir()
cache_file = os.path.join(configdir, &#39;.folders_cache.txt&#39;)
if os.path.exists(cache_file):
with open(cache_file, &#34;r&#34;) as f:
return [line.strip() for line in f if line.startswith(prefix)]
return []</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.helpers.get_config_dir"><code class="name flex">
<span>def <span class="ident">get_config_dir</span></span>(<span>)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def get_config_dir():
home = os.path.expanduser(&#34;~&#34;)
defaultdir = os.path.join(home, &#39;.config/conn&#39;)
pathfile = os.path.join(defaultdir, &#39;.folder&#39;)
try:
with open(pathfile, &#34;r&#34;) as f:
return f.read().strip()
except:
return defaultdir</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.helpers.nodes_completer"><code class="name flex">
<span>def <span class="ident">nodes_completer</span></span>(<span>prefix, parsed_args, **kwargs)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def nodes_completer(prefix, parsed_args, **kwargs):
configdir = get_config_dir()
cache_file = os.path.join(configdir, &#39;.fzf_nodes_cache.txt&#39;)
if os.path.exists(cache_file):
with open(cache_file, &#34;r&#34;) as f:
return [line.strip() for line in f if line.startswith(prefix)]
return []</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.helpers.profiles_completer"><code class="name flex">
<span>def <span class="ident">profiles_completer</span></span>(<span>prefix, parsed_args, **kwargs)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def profiles_completer(prefix, parsed_args, **kwargs):
configdir = get_config_dir()
cache_file = os.path.join(configdir, &#39;.profiles_cache.txt&#39;)
if os.path.exists(cache_file):
with open(cache_file, &#34;r&#34;) as f:
return [line.strip() for line in f if line.startswith(prefix)]
return []</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.helpers.toplevel_completer"><code class="name flex">
<span>def <span class="ident">toplevel_completer</span></span>(<span>prefix, parsed_args, **kwargs)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def toplevel_completer(prefix, parsed_args, **kwargs):
commands = [&#34;node&#34;, &#34;profile&#34;, &#34;move&#34;, &#34;mv&#34;, &#34;copy&#34;, &#34;cp&#34;, &#34;list&#34;, &#34;ls&#34;, &#34;bulk&#34;, &#34;export&#34;, &#34;import&#34;, &#34;ai&#34;, &#34;run&#34;, &#34;api&#34;, &#34;context&#34;, &#34;plugin&#34;, &#34;config&#34;, &#34;sync&#34;]
configdir = get_config_dir()
cache_file = os.path.join(configdir, &#39;.fzf_nodes_cache.txt&#39;)
nodes = []
if os.path.exists(cache_file):
with open(cache_file, &#34;r&#34;) as f:
nodes = [line.strip() for line in f if line.startswith(prefix)]
cache_folders = os.path.join(configdir, &#39;.folders_cache.txt&#39;)
if os.path.exists(cache_folders):
with open(cache_folders, &#34;r&#34;) as f:
nodes += [line.strip() for line in f if line.startswith(prefix)]
return [c for c in commands + nodes if c.startswith(prefix)]</code></pre>
</details>
<div class="desc"></div>
</dd>
</dl>
</section>
<section>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="connpy.cli" href="index.html">connpy.cli</a></code></li>
</ul>
</li>
<li><h3><a href="#header-functions">Functions</a></h3>
<ul class="two-column">
<li><code><a title="connpy.cli.helpers.choose" href="#connpy.cli.helpers.choose">choose</a></code></li>
<li><code><a title="connpy.cli.helpers.folders_completer" href="#connpy.cli.helpers.folders_completer">folders_completer</a></code></li>
<li><code><a title="connpy.cli.helpers.get_config_dir" href="#connpy.cli.helpers.get_config_dir">get_config_dir</a></code></li>
<li><code><a title="connpy.cli.helpers.nodes_completer" href="#connpy.cli.helpers.nodes_completer">nodes_completer</a></code></li>
<li><code><a title="connpy.cli.helpers.profiles_completer" href="#connpy.cli.helpers.profiles_completer">profiles_completer</a></code></li>
<li><code><a title="connpy.cli.helpers.toplevel_completer" href="#connpy.cli.helpers.toplevel_completer">toplevel_completer</a></code></li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.5</a>.</p>
</footer>
</body>
</html>
+278
View File
@@ -0,0 +1,278 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.5">
<title>connpy.cli.import_export_handler API documentation</title>
<meta name="description" content="">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source > summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible;min-width:max-content}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin:1em 0}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
/* Collapse source docstrings */
setTimeout(() => {
[...document.querySelectorAll('.hljs.language-python > .hljs-string')]
.filter(el => el.innerHTML.length > 200 && ['"""', "'''"].includes(el.innerHTML.substring(0, 3)))
.forEach(el => {
let d = document.createElement('details');
d.classList.add('hljs-string');
d.innerHTML = '<summary>"""</summary>' + el.innerHTML.substring(3);
el.replaceWith(d);
});
}, 100);
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>connpy.cli.import_export_handler</code></h1>
</header>
<section id="section-intro">
</section>
<section>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="connpy.cli.import_export_handler.ImportExportHandler"><code class="flex name class">
<span>class <span class="ident">ImportExportHandler</span></span>
<span>(</span><span>app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class ImportExportHandler:
def __init__(self, app):
self.app = app
self.forms = Forms(app)
def dispatch_import(self, args):
file_path = args.data[0]
try:
printer.warning(&#34;This could overwrite your current configuration!&#34;)
question = [inquirer.Confirm(&#34;import&#34;, message=f&#34;Are you sure you want to import {file_path}?&#34;)]
confirm = inquirer.prompt(question)
if confirm == None or not confirm[&#34;import&#34;]:
sys.exit(7)
self.app.services.import_export.import_from_file(file_path)
printer.success(f&#34;File {file_path} imported successfully.&#34;)
except ConnpyError as e:
printer.error(str(e))
sys.exit(1)
def dispatch_export(self, args):
file_path = args.data[0]
folders = args.data[1:] if len(args.data) &gt; 1 else None
try:
self.app.services.import_export.export_to_file(file_path, folders=folders)
printer.success(f&#34;File {file_path} generated successfully&#34;)
except ConnpyError as e:
printer.error(str(e))
sys.exit(1)
sys.exit()
def bulk(self, args):
if args.file and os.path.isfile(args.file[0]):
with open(args.file[0], &#39;r&#39;) as f:
lines = f.readlines()
# Expecting exactly 2 lines
if len(lines) &lt; 2:
printer.error(&#34;The file must contain at least two lines: one for nodes, one for hosts.&#34;)
sys.exit(11)
nodes = lines[0].strip()
hosts = lines[1].strip()
newnodes = self.forms.questions_bulk(nodes, hosts)
else:
newnodes = self.forms.questions_bulk()
if newnodes == False:
sys.exit(7)
if not self.app.case:
newnodes[&#34;location&#34;] = newnodes[&#34;location&#34;].lower()
newnodes[&#34;ids&#34;] = newnodes[&#34;ids&#34;].lower()
# Handle the case where location might be a file reference (e.g. from a prompt)
location = newnodes[&#34;location&#34;]
if location.startswith(&#34;@&#34;) and &#34;/&#34; in location:
# Extract the actual @folder part (e.g. @testall from @testall/.folders_cache.txt)
location = location.split(&#34;/&#34;)[0]
newnodes[&#34;location&#34;] = location
ids = newnodes[&#34;ids&#34;].split(&#34;,&#34;)
# Append location to each id for proper folder assignment
location = newnodes[&#34;location&#34;]
if location:
ids = [f&#34;{i}{location}&#34; for i in ids]
hosts = newnodes[&#34;host&#34;].split(&#34;,&#34;)
try:
count = self.app.services.nodes.bulk_add(ids, hosts, newnodes)
if count &gt; 0:
printer.success(f&#34;Successfully added {count} nodes.&#34;)
else:
printer.info(&#34;0 nodes added&#34;)
except ConnpyError as e:
printer.error(str(e))
sys.exit(1)</code></pre>
</details>
<div class="desc"></div>
<h3>Methods</h3>
<dl>
<dt id="connpy.cli.import_export_handler.ImportExportHandler.bulk"><code class="name flex">
<span>def <span class="ident">bulk</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def bulk(self, args):
if args.file and os.path.isfile(args.file[0]):
with open(args.file[0], &#39;r&#39;) as f:
lines = f.readlines()
# Expecting exactly 2 lines
if len(lines) &lt; 2:
printer.error(&#34;The file must contain at least two lines: one for nodes, one for hosts.&#34;)
sys.exit(11)
nodes = lines[0].strip()
hosts = lines[1].strip()
newnodes = self.forms.questions_bulk(nodes, hosts)
else:
newnodes = self.forms.questions_bulk()
if newnodes == False:
sys.exit(7)
if not self.app.case:
newnodes[&#34;location&#34;] = newnodes[&#34;location&#34;].lower()
newnodes[&#34;ids&#34;] = newnodes[&#34;ids&#34;].lower()
# Handle the case where location might be a file reference (e.g. from a prompt)
location = newnodes[&#34;location&#34;]
if location.startswith(&#34;@&#34;) and &#34;/&#34; in location:
# Extract the actual @folder part (e.g. @testall from @testall/.folders_cache.txt)
location = location.split(&#34;/&#34;)[0]
newnodes[&#34;location&#34;] = location
ids = newnodes[&#34;ids&#34;].split(&#34;,&#34;)
# Append location to each id for proper folder assignment
location = newnodes[&#34;location&#34;]
if location:
ids = [f&#34;{i}{location}&#34; for i in ids]
hosts = newnodes[&#34;host&#34;].split(&#34;,&#34;)
try:
count = self.app.services.nodes.bulk_add(ids, hosts, newnodes)
if count &gt; 0:
printer.success(f&#34;Successfully added {count} nodes.&#34;)
else:
printer.info(&#34;0 nodes added&#34;)
except ConnpyError as e:
printer.error(str(e))
sys.exit(1)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.import_export_handler.ImportExportHandler.dispatch_export"><code class="name flex">
<span>def <span class="ident">dispatch_export</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def dispatch_export(self, args):
file_path = args.data[0]
folders = args.data[1:] if len(args.data) &gt; 1 else None
try:
self.app.services.import_export.export_to_file(file_path, folders=folders)
printer.success(f&#34;File {file_path} generated successfully&#34;)
except ConnpyError as e:
printer.error(str(e))
sys.exit(1)
sys.exit()</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.import_export_handler.ImportExportHandler.dispatch_import"><code class="name flex">
<span>def <span class="ident">dispatch_import</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def dispatch_import(self, args):
file_path = args.data[0]
try:
printer.warning(&#34;This could overwrite your current configuration!&#34;)
question = [inquirer.Confirm(&#34;import&#34;, message=f&#34;Are you sure you want to import {file_path}?&#34;)]
confirm = inquirer.prompt(question)
if confirm == None or not confirm[&#34;import&#34;]:
sys.exit(7)
self.app.services.import_export.import_from_file(file_path)
printer.success(f&#34;File {file_path} imported successfully.&#34;)
except ConnpyError as e:
printer.error(str(e))
sys.exit(1)</code></pre>
</details>
<div class="desc"></div>
</dd>
</dl>
</dd>
</dl>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="connpy.cli" href="index.html">connpy.cli</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="connpy.cli.import_export_handler.ImportExportHandler" href="#connpy.cli.import_export_handler.ImportExportHandler">ImportExportHandler</a></code></h4>
<ul class="">
<li><code><a title="connpy.cli.import_export_handler.ImportExportHandler.bulk" href="#connpy.cli.import_export_handler.ImportExportHandler.bulk">bulk</a></code></li>
<li><code><a title="connpy.cli.import_export_handler.ImportExportHandler.dispatch_export" href="#connpy.cli.import_export_handler.ImportExportHandler.dispatch_export">dispatch_export</a></code></li>
<li><code><a title="connpy.cli.import_export_handler.ImportExportHandler.dispatch_import" href="#connpy.cli.import_export_handler.ImportExportHandler.dispatch_import">dispatch_import</a></code></li>
</ul>
</li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.5</a>.</p>
</footer>
</body>
</html>
+143
View File
@@ -0,0 +1,143 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.5">
<title>connpy.cli API documentation</title>
<meta name="description" content="">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source > summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible;min-width:max-content}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin:1em 0}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
/* Collapse source docstrings */
setTimeout(() => {
[...document.querySelectorAll('.hljs.language-python > .hljs-string')]
.filter(el => el.innerHTML.length > 200 && ['"""', "'''"].includes(el.innerHTML.substring(0, 3)))
.forEach(el => {
let d = document.createElement('details');
d.classList.add('hljs-string');
d.innerHTML = '<summary>"""</summary>' + el.innerHTML.substring(3);
el.replaceWith(d);
});
}, 100);
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>connpy.cli</code></h1>
</header>
<section id="section-intro">
</section>
<section>
<h2 class="section-title" id="header-submodules">Sub-modules</h2>
<dl>
<dt><code class="name"><a title="connpy.cli.ai_handler" href="ai_handler.html">connpy.cli.ai_handler</a></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt><code class="name"><a title="connpy.cli.api_handler" href="api_handler.html">connpy.cli.api_handler</a></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt><code class="name"><a title="connpy.cli.config_handler" href="config_handler.html">connpy.cli.config_handler</a></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt><code class="name"><a title="connpy.cli.context_handler" href="context_handler.html">connpy.cli.context_handler</a></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt><code class="name"><a title="connpy.cli.forms" href="forms.html">connpy.cli.forms</a></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt><code class="name"><a title="connpy.cli.help_text" href="help_text.html">connpy.cli.help_text</a></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt><code class="name"><a title="connpy.cli.helpers" href="helpers.html">connpy.cli.helpers</a></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt><code class="name"><a title="connpy.cli.import_export_handler" href="import_export_handler.html">connpy.cli.import_export_handler</a></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt><code class="name"><a title="connpy.cli.node_handler" href="node_handler.html">connpy.cli.node_handler</a></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt><code class="name"><a title="connpy.cli.plugin_handler" href="plugin_handler.html">connpy.cli.plugin_handler</a></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt><code class="name"><a title="connpy.cli.profile_handler" href="profile_handler.html">connpy.cli.profile_handler</a></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt><code class="name"><a title="connpy.cli.run_handler" href="run_handler.html">connpy.cli.run_handler</a></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt><code class="name"><a title="connpy.cli.sync_handler" href="sync_handler.html">connpy.cli.sync_handler</a></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt><code class="name"><a title="connpy.cli.validators" href="validators.html">connpy.cli.validators</a></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
</section>
<section>
</section>
<section>
</section>
<section>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="connpy" href="../index.html">connpy</a></code></li>
</ul>
</li>
<li><h3><a href="#header-submodules">Sub-modules</a></h3>
<ul>
<li><code><a title="connpy.cli.ai_handler" href="ai_handler.html">connpy.cli.ai_handler</a></code></li>
<li><code><a title="connpy.cli.api_handler" href="api_handler.html">connpy.cli.api_handler</a></code></li>
<li><code><a title="connpy.cli.config_handler" href="config_handler.html">connpy.cli.config_handler</a></code></li>
<li><code><a title="connpy.cli.context_handler" href="context_handler.html">connpy.cli.context_handler</a></code></li>
<li><code><a title="connpy.cli.forms" href="forms.html">connpy.cli.forms</a></code></li>
<li><code><a title="connpy.cli.help_text" href="help_text.html">connpy.cli.help_text</a></code></li>
<li><code><a title="connpy.cli.helpers" href="helpers.html">connpy.cli.helpers</a></code></li>
<li><code><a title="connpy.cli.import_export_handler" href="import_export_handler.html">connpy.cli.import_export_handler</a></code></li>
<li><code><a title="connpy.cli.node_handler" href="node_handler.html">connpy.cli.node_handler</a></code></li>
<li><code><a title="connpy.cli.plugin_handler" href="plugin_handler.html">connpy.cli.plugin_handler</a></code></li>
<li><code><a title="connpy.cli.profile_handler" href="profile_handler.html">connpy.cli.profile_handler</a></code></li>
<li><code><a title="connpy.cli.run_handler" href="run_handler.html">connpy.cli.run_handler</a></code></li>
<li><code><a title="connpy.cli.sync_handler" href="sync_handler.html">connpy.cli.sync_handler</a></code></li>
<li><code><a title="connpy.cli.validators" href="validators.html">connpy.cli.validators</a></code></li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.5</a>.</p>
</footer>
</body>
</html>
+604
View File
@@ -0,0 +1,604 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.5">
<title>connpy.cli.node_handler API documentation</title>
<meta name="description" content="">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source > summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible;min-width:max-content}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin:1em 0}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
/* Collapse source docstrings */
setTimeout(() => {
[...document.querySelectorAll('.hljs.language-python > .hljs-string')]
.filter(el => el.innerHTML.length > 200 && ['"""', "'''"].includes(el.innerHTML.substring(0, 3)))
.forEach(el => {
let d = document.createElement('details');
d.classList.add('hljs-string');
d.innerHTML = '<summary>"""</summary>' + el.innerHTML.substring(3);
el.replaceWith(d);
});
}, 100);
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>connpy.cli.node_handler</code></h1>
</header>
<section id="section-intro">
</section>
<section>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="connpy.cli.node_handler.NodeHandler"><code class="flex name class">
<span>class <span class="ident">NodeHandler</span></span>
<span>(</span><span>app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class NodeHandler:
def __init__(self, app):
self.app = app
self.forms = Forms(app)
def dispatch(self, args):
if not self.app.case and args.data != None:
args.data = args.data.lower()
actions = {&#34;version&#34;: self.version, &#34;connect&#34;: self.connect, &#34;add&#34;: self.add, &#34;del&#34;: self.delete, &#34;mod&#34;: self.modify, &#34;show&#34;: self.show}
return actions.get(args.action)(args)
def version(self, args):
from .._version import __version__
printer.info(f&#34;Connpy {__version__}&#34;)
def connect(self, args):
if args.data == None:
try:
matches = self.app.services.nodes.list_nodes()
except Exception as e:
printer.error(f&#34;Failed to list nodes: {e}&#34;)
sys.exit(1)
if len(matches) == 0:
printer.warning(&#34;There are no nodes created&#34;)
printer.info(&#34;try: connpy --help&#34;)
sys.exit(9)
else:
try:
matches = self.app.services.nodes.list_nodes(args.data)
except Exception:
matches = []
if len(matches) == 0:
printer.error(f&#34;{args.data} not found&#34;)
sys.exit(2)
elif len(matches) &gt; 1:
matches[0] = choose(self.app, matches, &#34;node&#34;, &#34;connect&#34;)
if matches[0] == None:
sys.exit(7)
try:
self.app.services.nodes.connect_node(
matches[0],
sftp=args.sftp,
debug=args.debug,
logger=self.app._service_logger
)
except ConnpyError as e:
printer.error(str(e))
sys.exit(1)
def delete(self, args):
if args.data == None:
printer.error(&#34;Missing argument node&#34;)
sys.exit(3)
is_folder = args.data.startswith(&#34;@&#34;)
try:
if is_folder:
matches = self.app.services.nodes.list_folders(args.data)
else:
matches = self.app.services.nodes.list_nodes(args.data)
except Exception:
matches = []
if len(matches) == 0:
printer.error(f&#34;{args.data} not found&#34;)
sys.exit(2)
printer.info(f&#34;Removing: {matches}&#34;)
question = [inquirer.Confirm(&#34;delete&#34;, message=&#34;Are you sure you want to continue?&#34;)]
confirm = inquirer.prompt(question)
if confirm == None or not confirm[&#34;delete&#34;]:
sys.exit(7)
try:
for item in matches:
self.app.services.nodes.delete_node(item, is_folder=is_folder)
if len(matches) == 1:
printer.success(f&#34;{matches[0]} deleted successfully&#34;)
else:
printer.success(f&#34;{len(matches)} items deleted successfully&#34;)
except ConnpyError as e:
printer.error(str(e))
sys.exit(1)
def add(self, args):
try:
args.data = self.app._type_node(args.data)
except ValueError as e:
printer.error(str(e))
sys.exit(3)
if args.data == None:
printer.error(&#34;Missing argument node&#34;)
sys.exit(3)
is_folder = args.data.startswith(&#34;@&#34;)
try:
if is_folder:
uniques = self.app.services.nodes.explode_unique(args.data)
if not uniques:
raise InvalidConfigurationError(f&#34;Invalid folder {args.data}&#34;)
self.app.services.nodes.add_node(args.data, {}, is_folder=True)
printer.success(f&#34;{args.data} added successfully&#34;)
else:
if args.data in self.app.nodes_list:
printer.error(f&#34;Node &#39;{args.data}&#39; already exists.&#34;)
sys.exit(1)
uniques = self.app.services.nodes.explode_unique(args.data)
printer.console.print(Markdown(get_instructions()))
new_node_data = self.forms.questions_nodes(args.data, uniques)
if not new_node_data:
sys.exit(7)
self.app.services.nodes.add_node(args.data, new_node_data)
printer.success(f&#34;{args.data} added successfully&#34;)
except ConnpyError as e:
printer.error(str(e))
sys.exit(1)
def show(self, args):
if args.data == None:
printer.error(&#34;Missing argument node&#34;)
sys.exit(3)
try:
matches = self.app.services.nodes.list_nodes(args.data)
except Exception:
matches = []
if len(matches) == 0:
printer.error(f&#34;{args.data} not found&#34;)
sys.exit(2)
elif len(matches) &gt; 1:
matches[0] = choose(self.app, matches, &#34;node&#34;, &#34;show&#34;)
if matches[0] == None:
sys.exit(7)
try:
node = self.app.services.nodes.get_node_details(matches[0])
yaml_output = yaml.dump(node, sort_keys=False, default_flow_style=False)
printer.data(matches[0], yaml_output)
except ConnpyError as e:
printer.error(str(e))
sys.exit(1)
def modify(self, args):
if args.data == None:
printer.error(&#34;Missing argument node&#34;)
sys.exit(3)
try:
matches = self.app.services.nodes.list_nodes(args.data)
except Exception:
matches = []
if len(matches) == 0:
printer.error(f&#34;No connection found with filter: {args.data}&#34;)
sys.exit(2)
unique = matches[0] if len(matches) == 1 else None
uniques = self.app.services.nodes.explode_unique(unique) if unique else {&#34;id&#34;: None, &#34;folder&#34;: None}
printer.info(f&#34;Editing: {matches}&#34;)
node_details = {}
for i in matches:
node_details[i] = self.app.services.nodes.get_node_details(i)
edits = self.forms.questions_edit()
if edits == None:
sys.exit(7)
# Use first match as base for defaults if multiple matches exist
base_unique = matches[0]
base_uniques = self.app.services.nodes.explode_unique(base_unique)
updatenode = self.forms.questions_nodes(base_unique, base_uniques, edit=edits)
if not updatenode:
sys.exit(7)
try:
if len(matches) == 1:
# Comparison for &#34;Nothing to do&#34;
current = node_details[matches[0]].copy()
current.update(uniques)
current[&#34;type&#34;] = &#34;connection&#34;
if sorted(updatenode.items()) == sorted(current.items()):
printer.info(&#34;Nothing to do here&#34;)
return
self.app.services.nodes.update_node(matches[0], updatenode)
printer.success(f&#34;{args.data} edited successfully&#34;)
else:
editcount = 0
for k in matches:
updated_item = self.app.services.nodes.explode_unique(k)
updated_item[&#34;type&#34;] = &#34;connection&#34;
updated_item.update(node_details[k])
this_item_changed = False
for key, should_edit in edits.items():
if should_edit:
this_item_changed = True
updated_item[key] = updatenode[key]
if this_item_changed:
editcount += 1
self.app.services.nodes.update_node(k, updated_item)
if editcount == 0:
printer.info(&#34;Nothing to do here&#34;)
else:
printer.success(f&#34;{matches} edited successfully&#34;)
except ConnpyError as e:
printer.error(str(e))
sys.exit(1)</code></pre>
</details>
<div class="desc"></div>
<h3>Methods</h3>
<dl>
<dt id="connpy.cli.node_handler.NodeHandler.add"><code class="name flex">
<span>def <span class="ident">add</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def add(self, args):
try:
args.data = self.app._type_node(args.data)
except ValueError as e:
printer.error(str(e))
sys.exit(3)
if args.data == None:
printer.error(&#34;Missing argument node&#34;)
sys.exit(3)
is_folder = args.data.startswith(&#34;@&#34;)
try:
if is_folder:
uniques = self.app.services.nodes.explode_unique(args.data)
if not uniques:
raise InvalidConfigurationError(f&#34;Invalid folder {args.data}&#34;)
self.app.services.nodes.add_node(args.data, {}, is_folder=True)
printer.success(f&#34;{args.data} added successfully&#34;)
else:
if args.data in self.app.nodes_list:
printer.error(f&#34;Node &#39;{args.data}&#39; already exists.&#34;)
sys.exit(1)
uniques = self.app.services.nodes.explode_unique(args.data)
printer.console.print(Markdown(get_instructions()))
new_node_data = self.forms.questions_nodes(args.data, uniques)
if not new_node_data:
sys.exit(7)
self.app.services.nodes.add_node(args.data, new_node_data)
printer.success(f&#34;{args.data} added successfully&#34;)
except ConnpyError as e:
printer.error(str(e))
sys.exit(1)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.node_handler.NodeHandler.connect"><code class="name flex">
<span>def <span class="ident">connect</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def connect(self, args):
if args.data == None:
try:
matches = self.app.services.nodes.list_nodes()
except Exception as e:
printer.error(f&#34;Failed to list nodes: {e}&#34;)
sys.exit(1)
if len(matches) == 0:
printer.warning(&#34;There are no nodes created&#34;)
printer.info(&#34;try: connpy --help&#34;)
sys.exit(9)
else:
try:
matches = self.app.services.nodes.list_nodes(args.data)
except Exception:
matches = []
if len(matches) == 0:
printer.error(f&#34;{args.data} not found&#34;)
sys.exit(2)
elif len(matches) &gt; 1:
matches[0] = choose(self.app, matches, &#34;node&#34;, &#34;connect&#34;)
if matches[0] == None:
sys.exit(7)
try:
self.app.services.nodes.connect_node(
matches[0],
sftp=args.sftp,
debug=args.debug,
logger=self.app._service_logger
)
except ConnpyError as e:
printer.error(str(e))
sys.exit(1)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.node_handler.NodeHandler.delete"><code class="name flex">
<span>def <span class="ident">delete</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def delete(self, args):
if args.data == None:
printer.error(&#34;Missing argument node&#34;)
sys.exit(3)
is_folder = args.data.startswith(&#34;@&#34;)
try:
if is_folder:
matches = self.app.services.nodes.list_folders(args.data)
else:
matches = self.app.services.nodes.list_nodes(args.data)
except Exception:
matches = []
if len(matches) == 0:
printer.error(f&#34;{args.data} not found&#34;)
sys.exit(2)
printer.info(f&#34;Removing: {matches}&#34;)
question = [inquirer.Confirm(&#34;delete&#34;, message=&#34;Are you sure you want to continue?&#34;)]
confirm = inquirer.prompt(question)
if confirm == None or not confirm[&#34;delete&#34;]:
sys.exit(7)
try:
for item in matches:
self.app.services.nodes.delete_node(item, is_folder=is_folder)
if len(matches) == 1:
printer.success(f&#34;{matches[0]} deleted successfully&#34;)
else:
printer.success(f&#34;{len(matches)} items deleted successfully&#34;)
except ConnpyError as e:
printer.error(str(e))
sys.exit(1)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.node_handler.NodeHandler.dispatch"><code class="name flex">
<span>def <span class="ident">dispatch</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def dispatch(self, args):
if not self.app.case and args.data != None:
args.data = args.data.lower()
actions = {&#34;version&#34;: self.version, &#34;connect&#34;: self.connect, &#34;add&#34;: self.add, &#34;del&#34;: self.delete, &#34;mod&#34;: self.modify, &#34;show&#34;: self.show}
return actions.get(args.action)(args)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.node_handler.NodeHandler.modify"><code class="name flex">
<span>def <span class="ident">modify</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def modify(self, args):
if args.data == None:
printer.error(&#34;Missing argument node&#34;)
sys.exit(3)
try:
matches = self.app.services.nodes.list_nodes(args.data)
except Exception:
matches = []
if len(matches) == 0:
printer.error(f&#34;No connection found with filter: {args.data}&#34;)
sys.exit(2)
unique = matches[0] if len(matches) == 1 else None
uniques = self.app.services.nodes.explode_unique(unique) if unique else {&#34;id&#34;: None, &#34;folder&#34;: None}
printer.info(f&#34;Editing: {matches}&#34;)
node_details = {}
for i in matches:
node_details[i] = self.app.services.nodes.get_node_details(i)
edits = self.forms.questions_edit()
if edits == None:
sys.exit(7)
# Use first match as base for defaults if multiple matches exist
base_unique = matches[0]
base_uniques = self.app.services.nodes.explode_unique(base_unique)
updatenode = self.forms.questions_nodes(base_unique, base_uniques, edit=edits)
if not updatenode:
sys.exit(7)
try:
if len(matches) == 1:
# Comparison for &#34;Nothing to do&#34;
current = node_details[matches[0]].copy()
current.update(uniques)
current[&#34;type&#34;] = &#34;connection&#34;
if sorted(updatenode.items()) == sorted(current.items()):
printer.info(&#34;Nothing to do here&#34;)
return
self.app.services.nodes.update_node(matches[0], updatenode)
printer.success(f&#34;{args.data} edited successfully&#34;)
else:
editcount = 0
for k in matches:
updated_item = self.app.services.nodes.explode_unique(k)
updated_item[&#34;type&#34;] = &#34;connection&#34;
updated_item.update(node_details[k])
this_item_changed = False
for key, should_edit in edits.items():
if should_edit:
this_item_changed = True
updated_item[key] = updatenode[key]
if this_item_changed:
editcount += 1
self.app.services.nodes.update_node(k, updated_item)
if editcount == 0:
printer.info(&#34;Nothing to do here&#34;)
else:
printer.success(f&#34;{matches} edited successfully&#34;)
except ConnpyError as e:
printer.error(str(e))
sys.exit(1)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.node_handler.NodeHandler.show"><code class="name flex">
<span>def <span class="ident">show</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def show(self, args):
if args.data == None:
printer.error(&#34;Missing argument node&#34;)
sys.exit(3)
try:
matches = self.app.services.nodes.list_nodes(args.data)
except Exception:
matches = []
if len(matches) == 0:
printer.error(f&#34;{args.data} not found&#34;)
sys.exit(2)
elif len(matches) &gt; 1:
matches[0] = choose(self.app, matches, &#34;node&#34;, &#34;show&#34;)
if matches[0] == None:
sys.exit(7)
try:
node = self.app.services.nodes.get_node_details(matches[0])
yaml_output = yaml.dump(node, sort_keys=False, default_flow_style=False)
printer.data(matches[0], yaml_output)
except ConnpyError as e:
printer.error(str(e))
sys.exit(1)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.node_handler.NodeHandler.version"><code class="name flex">
<span>def <span class="ident">version</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def version(self, args):
from .._version import __version__
printer.info(f&#34;Connpy {__version__}&#34;)</code></pre>
</details>
<div class="desc"></div>
</dd>
</dl>
</dd>
</dl>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="connpy.cli" href="index.html">connpy.cli</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="connpy.cli.node_handler.NodeHandler" href="#connpy.cli.node_handler.NodeHandler">NodeHandler</a></code></h4>
<ul class="two-column">
<li><code><a title="connpy.cli.node_handler.NodeHandler.add" href="#connpy.cli.node_handler.NodeHandler.add">add</a></code></li>
<li><code><a title="connpy.cli.node_handler.NodeHandler.connect" href="#connpy.cli.node_handler.NodeHandler.connect">connect</a></code></li>
<li><code><a title="connpy.cli.node_handler.NodeHandler.delete" href="#connpy.cli.node_handler.NodeHandler.delete">delete</a></code></li>
<li><code><a title="connpy.cli.node_handler.NodeHandler.dispatch" href="#connpy.cli.node_handler.NodeHandler.dispatch">dispatch</a></code></li>
<li><code><a title="connpy.cli.node_handler.NodeHandler.modify" href="#connpy.cli.node_handler.NodeHandler.modify">modify</a></code></li>
<li><code><a title="connpy.cli.node_handler.NodeHandler.show" href="#connpy.cli.node_handler.NodeHandler.show">show</a></code></li>
<li><code><a title="connpy.cli.node_handler.NodeHandler.version" href="#connpy.cli.node_handler.NodeHandler.version">version</a></code></li>
</ul>
</li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.5</a>.</p>
</footer>
</body>
</html>
+391
View File
@@ -0,0 +1,391 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.5">
<title>connpy.cli.plugin_handler API documentation</title>
<meta name="description" content="">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source > summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible;min-width:max-content}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin:1em 0}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
/* Collapse source docstrings */
setTimeout(() => {
[...document.querySelectorAll('.hljs.language-python > .hljs-string')]
.filter(el => el.innerHTML.length > 200 && ['"""', "'''"].includes(el.innerHTML.substring(0, 3)))
.forEach(el => {
let d = document.createElement('details');
d.classList.add('hljs-string');
d.innerHTML = '<summary>"""</summary>' + el.innerHTML.substring(3);
el.replaceWith(d);
});
}, 100);
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>connpy.cli.plugin_handler</code></h1>
</header>
<section id="section-intro">
</section>
<section>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="connpy.cli.plugin_handler.PluginHandler"><code class="flex name class">
<span>class <span class="ident">PluginHandler</span></span>
<span>(</span><span>app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class PluginHandler:
def __init__(self, app):
self.app = app
def dispatch(self, args):
try:
# We determine the target PluginService/PluginStub based on standard &#39;mode&#39;
# But wait, local plugins should go to app.services._init_local version
# Or we can just use the provided app.services.plugins and pass the appropriate grpc calls if needed.
is_remote = getattr(args, &#34;remote&#34;, False)
if is_remote and self.app.services.mode != &#34;remote&#34;:
printer.error(&#34;Cannot use --remote flag when not running in remote mode.&#34;)
return
if args.add:
self.app.services.plugins.add_plugin(args.add[0], args.add[1])
printer.success(f&#34;Plugin {args.add[0]} added successfully{&#39; remotely&#39; if is_remote else &#39;&#39;}.&#34;)
elif args.update:
self.app.services.plugins.add_plugin(args.update[0], args.update[1], update=True)
printer.success(f&#34;Plugin {args.update[0]} updated successfully{&#39; remotely&#39; if is_remote else &#39;&#39;}.&#34;)
elif args.delete:
self.app.services.plugins.delete_plugin(args.delete[0])
printer.success(f&#34;Plugin {args.delete[0]} deleted successfully{&#39; remotely&#39; if is_remote else &#39;&#39;}.&#34;)
elif args.enable:
name = args.enable[0]
if is_remote:
self.app.plugins.preferences[name] = &#34;remote&#34;
else:
if name in self.app.plugins.preferences:
del self.app.plugins.preferences[name]
self.app.plugins._save_preferences(self.app.services.config_svc.get_default_dir())
# Always try to enable it locally (remove .bkp) if it exists
# regardless of mode, to keep files consistent with &#34;enabled&#34; state
try:
# We use a local service instance to ensure we touch local files
from ..services.plugin_service import PluginService
local_svc = PluginService(self.app.services.config)
local_svc.enable_plugin(name)
except Exception:
pass # Ignore if not found locally or already enabled
if is_remote and self.app.services.mode == &#34;remote&#34;:
self.app.services.plugins.enable_plugin(name)
printer.success(f&#34;Plugin {name} enabled successfully{&#39; remotely&#39; if is_remote else &#39; locally&#39;}.&#34;)
elif args.disable:
name = args.disable[0]
success = False
if is_remote:
if self.app.services.mode == &#34;remote&#34;:
self.app.services.plugins.disable_plugin(name)
success = True
else:
# Disable locally
from ..services.plugin_service import PluginService
local_svc = PluginService(self.app.services.config)
try:
if local_svc.disable_plugin(name):
success = True
except Exception as e:
printer.warning(f&#34;Could not disable local plugin: {e}&#34;)
if success:
printer.success(f&#34;Plugin {name} disabled successfully{&#39; remotely&#39; if is_remote else &#39; locally&#39;}.&#34;)
# If any remote operation was performed, trigger a sync to update local cache immediately
if is_remote and self.app.services.mode == &#34;remote&#34;:
try:
import os
cache_dir = os.path.join(self.app.services.config_svc.get_default_dir(), &#34;remote_plugins&#34;)
# We use a dummy subparser choice check bypass by passing force_sync=True
# or just letting the hasher handle it.
self.app.plugins._import_remote_plugins_to_argparse(
self.app.services.plugins,
self.app.subparsers, # We&#39;ll need to make sure this is available
cache_dir,
force_sync=True
)
except Exception:
pass
elif getattr(args, &#34;sync&#34;, False):
# The actual sync logic is performed in connapp.py during init
# if the --sync flag is detected in sys.argv
printer.success(&#34;Remote plugins synchronized successfully.&#34;)
elif args.list:
# We need to fetch both local and remote if in remote mode
local_plugins = {}
remote_plugins = {}
# Fetch depending on mode
if self.app.services.mode == &#34;remote&#34;:
# For local we need to instantiate a local plugin service bypassing stub
from ..services.plugin_service import PluginService
local_svc = PluginService(self.app.services.config)
local_plugins = local_svc.list_plugins()
remote_plugins = self.app.services.plugins.list_plugins()
else:
local_plugins = self.app.services.plugins.list_plugins()
from rich.table import Table
table = Table(title=&#34;Available Plugins&#34;, show_header=True, header_style=&#34;bold cyan&#34;)
table.add_column(&#34;Plugin&#34;, style=&#34;cyan&#34;)
table.add_column(&#34;State&#34;, style=&#34;bold&#34;)
table.add_column(&#34;Origin&#34;, style=&#34;magenta&#34;)
# Populate local plugins
for name, details in local_plugins.items():
state = &#34;Disabled&#34; if not details.get(&#34;enabled&#34;, True) else &#34;Active&#34;
color = &#34;red&#34; if state == &#34;Disabled&#34; else &#34;green&#34;
if self.app.services.mode == &#34;remote&#34; and state == &#34;Active&#34;:
if self.app.plugins.preferences.get(name) == &#34;remote&#34;:
state = &#34;Shadowed (Override by Remote)&#34;
color = &#34;yellow&#34;
table.add_row(name, f&#34;[{color}]{state}[/{color}]&#34;, &#34;Local&#34;)
# Populate remote plugins
if self.app.services.mode == &#34;remote&#34;:
for name, details in remote_plugins.items():
state = &#34;Disabled&#34; if not details.get(&#34;enabled&#34;, True) else &#34;Active&#34;
color = &#34;red&#34; if state == &#34;Disabled&#34; else &#34;green&#34;
if state == &#34;Active&#34;:
pref = self.app.plugins.preferences.get(name, &#34;local&#34;)
# If preference isn&#39;t remote and the plugin exists locally, local takes priority
if pref != &#34;remote&#34; and name in local_plugins:
state = &#34;Shadowed (Override by Local)&#34;
color = &#34;yellow&#34;
table.add_row(name, f&#34;[{color}]{state}[/{color}]&#34;, &#34;Remote&#34;)
if not local_plugins and not remote_plugins:
printer.console.print(&#34; No plugins found.&#34;)
else:
printer.console.print(table)
except ConnpyError as e:
printer.error(str(e))
sys.exit(1)</code></pre>
</details>
<div class="desc"></div>
<h3>Methods</h3>
<dl>
<dt id="connpy.cli.plugin_handler.PluginHandler.dispatch"><code class="name flex">
<span>def <span class="ident">dispatch</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def dispatch(self, args):
try:
# We determine the target PluginService/PluginStub based on standard &#39;mode&#39;
# But wait, local plugins should go to app.services._init_local version
# Or we can just use the provided app.services.plugins and pass the appropriate grpc calls if needed.
is_remote = getattr(args, &#34;remote&#34;, False)
if is_remote and self.app.services.mode != &#34;remote&#34;:
printer.error(&#34;Cannot use --remote flag when not running in remote mode.&#34;)
return
if args.add:
self.app.services.plugins.add_plugin(args.add[0], args.add[1])
printer.success(f&#34;Plugin {args.add[0]} added successfully{&#39; remotely&#39; if is_remote else &#39;&#39;}.&#34;)
elif args.update:
self.app.services.plugins.add_plugin(args.update[0], args.update[1], update=True)
printer.success(f&#34;Plugin {args.update[0]} updated successfully{&#39; remotely&#39; if is_remote else &#39;&#39;}.&#34;)
elif args.delete:
self.app.services.plugins.delete_plugin(args.delete[0])
printer.success(f&#34;Plugin {args.delete[0]} deleted successfully{&#39; remotely&#39; if is_remote else &#39;&#39;}.&#34;)
elif args.enable:
name = args.enable[0]
if is_remote:
self.app.plugins.preferences[name] = &#34;remote&#34;
else:
if name in self.app.plugins.preferences:
del self.app.plugins.preferences[name]
self.app.plugins._save_preferences(self.app.services.config_svc.get_default_dir())
# Always try to enable it locally (remove .bkp) if it exists
# regardless of mode, to keep files consistent with &#34;enabled&#34; state
try:
# We use a local service instance to ensure we touch local files
from ..services.plugin_service import PluginService
local_svc = PluginService(self.app.services.config)
local_svc.enable_plugin(name)
except Exception:
pass # Ignore if not found locally or already enabled
if is_remote and self.app.services.mode == &#34;remote&#34;:
self.app.services.plugins.enable_plugin(name)
printer.success(f&#34;Plugin {name} enabled successfully{&#39; remotely&#39; if is_remote else &#39; locally&#39;}.&#34;)
elif args.disable:
name = args.disable[0]
success = False
if is_remote:
if self.app.services.mode == &#34;remote&#34;:
self.app.services.plugins.disable_plugin(name)
success = True
else:
# Disable locally
from ..services.plugin_service import PluginService
local_svc = PluginService(self.app.services.config)
try:
if local_svc.disable_plugin(name):
success = True
except Exception as e:
printer.warning(f&#34;Could not disable local plugin: {e}&#34;)
if success:
printer.success(f&#34;Plugin {name} disabled successfully{&#39; remotely&#39; if is_remote else &#39; locally&#39;}.&#34;)
# If any remote operation was performed, trigger a sync to update local cache immediately
if is_remote and self.app.services.mode == &#34;remote&#34;:
try:
import os
cache_dir = os.path.join(self.app.services.config_svc.get_default_dir(), &#34;remote_plugins&#34;)
# We use a dummy subparser choice check bypass by passing force_sync=True
# or just letting the hasher handle it.
self.app.plugins._import_remote_plugins_to_argparse(
self.app.services.plugins,
self.app.subparsers, # We&#39;ll need to make sure this is available
cache_dir,
force_sync=True
)
except Exception:
pass
elif getattr(args, &#34;sync&#34;, False):
# The actual sync logic is performed in connapp.py during init
# if the --sync flag is detected in sys.argv
printer.success(&#34;Remote plugins synchronized successfully.&#34;)
elif args.list:
# We need to fetch both local and remote if in remote mode
local_plugins = {}
remote_plugins = {}
# Fetch depending on mode
if self.app.services.mode == &#34;remote&#34;:
# For local we need to instantiate a local plugin service bypassing stub
from ..services.plugin_service import PluginService
local_svc = PluginService(self.app.services.config)
local_plugins = local_svc.list_plugins()
remote_plugins = self.app.services.plugins.list_plugins()
else:
local_plugins = self.app.services.plugins.list_plugins()
from rich.table import Table
table = Table(title=&#34;Available Plugins&#34;, show_header=True, header_style=&#34;bold cyan&#34;)
table.add_column(&#34;Plugin&#34;, style=&#34;cyan&#34;)
table.add_column(&#34;State&#34;, style=&#34;bold&#34;)
table.add_column(&#34;Origin&#34;, style=&#34;magenta&#34;)
# Populate local plugins
for name, details in local_plugins.items():
state = &#34;Disabled&#34; if not details.get(&#34;enabled&#34;, True) else &#34;Active&#34;
color = &#34;red&#34; if state == &#34;Disabled&#34; else &#34;green&#34;
if self.app.services.mode == &#34;remote&#34; and state == &#34;Active&#34;:
if self.app.plugins.preferences.get(name) == &#34;remote&#34;:
state = &#34;Shadowed (Override by Remote)&#34;
color = &#34;yellow&#34;
table.add_row(name, f&#34;[{color}]{state}[/{color}]&#34;, &#34;Local&#34;)
# Populate remote plugins
if self.app.services.mode == &#34;remote&#34;:
for name, details in remote_plugins.items():
state = &#34;Disabled&#34; if not details.get(&#34;enabled&#34;, True) else &#34;Active&#34;
color = &#34;red&#34; if state == &#34;Disabled&#34; else &#34;green&#34;
if state == &#34;Active&#34;:
pref = self.app.plugins.preferences.get(name, &#34;local&#34;)
# If preference isn&#39;t remote and the plugin exists locally, local takes priority
if pref != &#34;remote&#34; and name in local_plugins:
state = &#34;Shadowed (Override by Local)&#34;
color = &#34;yellow&#34;
table.add_row(name, f&#34;[{color}]{state}[/{color}]&#34;, &#34;Remote&#34;)
if not local_plugins and not remote_plugins:
printer.console.print(&#34; No plugins found.&#34;)
else:
printer.console.print(table)
except ConnpyError as e:
printer.error(str(e))
sys.exit(1)</code></pre>
</details>
<div class="desc"></div>
</dd>
</dl>
</dd>
</dl>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="connpy.cli" href="index.html">connpy.cli</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="connpy.cli.plugin_handler.PluginHandler" href="#connpy.cli.plugin_handler.PluginHandler">PluginHandler</a></code></h4>
<ul class="">
<li><code><a title="connpy.cli.plugin_handler.PluginHandler.dispatch" href="#connpy.cli.plugin_handler.PluginHandler.dispatch">dispatch</a></code></li>
</ul>
</li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.5</a>.</p>
</footer>
</body>
</html>
+320
View File
@@ -0,0 +1,320 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.5">
<title>connpy.cli.profile_handler API documentation</title>
<meta name="description" content="">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source > summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible;min-width:max-content}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin:1em 0}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
/* Collapse source docstrings */
setTimeout(() => {
[...document.querySelectorAll('.hljs.language-python > .hljs-string')]
.filter(el => el.innerHTML.length > 200 && ['"""', "'''"].includes(el.innerHTML.substring(0, 3)))
.forEach(el => {
let d = document.createElement('details');
d.classList.add('hljs-string');
d.innerHTML = '<summary>"""</summary>' + el.innerHTML.substring(3);
el.replaceWith(d);
});
}, 100);
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>connpy.cli.profile_handler</code></h1>
</header>
<section id="section-intro">
</section>
<section>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="connpy.cli.profile_handler.ProfileHandler"><code class="flex name class">
<span>class <span class="ident">ProfileHandler</span></span>
<span>(</span><span>app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class ProfileHandler:
def __init__(self, app):
self.app = app
self.forms = Forms(app)
def dispatch(self, args):
if not self.app.case:
args.data[0] = args.data[0].lower()
actions = {&#34;add&#34;: self.add, &#34;del&#34;: self.delete, &#34;mod&#34;: self.modify, &#34;show&#34;: self.show}
return actions.get(args.action)(args)
def delete(self, args):
name = args.data[0]
try:
self.app.services.profiles.get_profile(name)
except ProfileNotFoundError:
printer.error(f&#34;{name} not found&#34;)
sys.exit(2)
if name == &#34;default&#34;:
printer.error(&#34;Can&#39;t delete default profile&#34;)
sys.exit(6)
question = [inquirer.Confirm(&#34;delete&#34;, message=f&#34;Are you sure you want to delete {name}?&#34;)]
confirm = inquirer.prompt(question)
if confirm == None or not confirm[&#34;delete&#34;]:
sys.exit(7)
try:
self.app.services.profiles.delete_profile(name)
printer.success(f&#34;{name} deleted successfully&#34;)
except ConnpyError as e:
printer.error(str(e))
sys.exit(8)
def show(self, args):
try:
profile = self.app.services.profiles.get_profile(args.data[0])
yaml_output = yaml.dump(profile, sort_keys=False, default_flow_style=False)
printer.data(args.data[0], yaml_output)
except ProfileNotFoundError:
printer.error(f&#34;{args.data[0]} not found&#34;)
sys.exit(2)
def add(self, args):
name = args.data[0]
if name in self.app.services.profiles.list_profiles():
printer.error(f&#34;Profile &#39;{name}&#39; already exists.&#34;)
sys.exit(4)
new_profile_data = self.forms.questions_profiles(name)
if not new_profile_data:
sys.exit(7)
try:
self.app.services.profiles.add_profile(name, new_profile_data)
printer.success(f&#34;{name} added successfully&#34;)
except ConnpyError as e:
printer.error(str(e))
sys.exit(1)
def modify(self, args):
name = args.data[0]
try:
profile = self.app.services.profiles.get_profile(name, resolve=False)
except ProfileNotFoundError:
printer.error(f&#34;Profile &#39;{name}&#39; not found&#34;)
sys.exit(2)
old_profile = {&#34;id&#34;: name, **profile}
edits = self.forms.questions_edit()
if edits == None:
sys.exit(7)
update_profile_data = self.forms.questions_profiles(name, edit=edits)
if not update_profile_data:
sys.exit(7)
if sorted(update_profile_data.items()) == sorted(old_profile.items()):
printer.info(&#34;Nothing to do here&#34;)
return
try:
self.app.services.profiles.update_profile(name, update_profile_data)
printer.success(f&#34;{name} edited successfully&#34;)
except ConnpyError as e:
printer.error(str(e))
sys.exit(1)</code></pre>
</details>
<div class="desc"></div>
<h3>Methods</h3>
<dl>
<dt id="connpy.cli.profile_handler.ProfileHandler.add"><code class="name flex">
<span>def <span class="ident">add</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def add(self, args):
name = args.data[0]
if name in self.app.services.profiles.list_profiles():
printer.error(f&#34;Profile &#39;{name}&#39; already exists.&#34;)
sys.exit(4)
new_profile_data = self.forms.questions_profiles(name)
if not new_profile_data:
sys.exit(7)
try:
self.app.services.profiles.add_profile(name, new_profile_data)
printer.success(f&#34;{name} added successfully&#34;)
except ConnpyError as e:
printer.error(str(e))
sys.exit(1)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.profile_handler.ProfileHandler.delete"><code class="name flex">
<span>def <span class="ident">delete</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def delete(self, args):
name = args.data[0]
try:
self.app.services.profiles.get_profile(name)
except ProfileNotFoundError:
printer.error(f&#34;{name} not found&#34;)
sys.exit(2)
if name == &#34;default&#34;:
printer.error(&#34;Can&#39;t delete default profile&#34;)
sys.exit(6)
question = [inquirer.Confirm(&#34;delete&#34;, message=f&#34;Are you sure you want to delete {name}?&#34;)]
confirm = inquirer.prompt(question)
if confirm == None or not confirm[&#34;delete&#34;]:
sys.exit(7)
try:
self.app.services.profiles.delete_profile(name)
printer.success(f&#34;{name} deleted successfully&#34;)
except ConnpyError as e:
printer.error(str(e))
sys.exit(8)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.profile_handler.ProfileHandler.dispatch"><code class="name flex">
<span>def <span class="ident">dispatch</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def dispatch(self, args):
if not self.app.case:
args.data[0] = args.data[0].lower()
actions = {&#34;add&#34;: self.add, &#34;del&#34;: self.delete, &#34;mod&#34;: self.modify, &#34;show&#34;: self.show}
return actions.get(args.action)(args)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.profile_handler.ProfileHandler.modify"><code class="name flex">
<span>def <span class="ident">modify</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def modify(self, args):
name = args.data[0]
try:
profile = self.app.services.profiles.get_profile(name, resolve=False)
except ProfileNotFoundError:
printer.error(f&#34;Profile &#39;{name}&#39; not found&#34;)
sys.exit(2)
old_profile = {&#34;id&#34;: name, **profile}
edits = self.forms.questions_edit()
if edits == None:
sys.exit(7)
update_profile_data = self.forms.questions_profiles(name, edit=edits)
if not update_profile_data:
sys.exit(7)
if sorted(update_profile_data.items()) == sorted(old_profile.items()):
printer.info(&#34;Nothing to do here&#34;)
return
try:
self.app.services.profiles.update_profile(name, update_profile_data)
printer.success(f&#34;{name} edited successfully&#34;)
except ConnpyError as e:
printer.error(str(e))
sys.exit(1)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.profile_handler.ProfileHandler.show"><code class="name flex">
<span>def <span class="ident">show</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def show(self, args):
try:
profile = self.app.services.profiles.get_profile(args.data[0])
yaml_output = yaml.dump(profile, sort_keys=False, default_flow_style=False)
printer.data(args.data[0], yaml_output)
except ProfileNotFoundError:
printer.error(f&#34;{args.data[0]} not found&#34;)
sys.exit(2)</code></pre>
</details>
<div class="desc"></div>
</dd>
</dl>
</dd>
</dl>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="connpy.cli" href="index.html">connpy.cli</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="connpy.cli.profile_handler.ProfileHandler" href="#connpy.cli.profile_handler.ProfileHandler">ProfileHandler</a></code></h4>
<ul class="">
<li><code><a title="connpy.cli.profile_handler.ProfileHandler.add" href="#connpy.cli.profile_handler.ProfileHandler.add">add</a></code></li>
<li><code><a title="connpy.cli.profile_handler.ProfileHandler.delete" href="#connpy.cli.profile_handler.ProfileHandler.delete">delete</a></code></li>
<li><code><a title="connpy.cli.profile_handler.ProfileHandler.dispatch" href="#connpy.cli.profile_handler.ProfileHandler.dispatch">dispatch</a></code></li>
<li><code><a title="connpy.cli.profile_handler.ProfileHandler.modify" href="#connpy.cli.profile_handler.ProfileHandler.modify">modify</a></code></li>
<li><code><a title="connpy.cli.profile_handler.ProfileHandler.show" href="#connpy.cli.profile_handler.ProfileHandler.show">show</a></code></li>
</ul>
</li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.5</a>.</p>
</footer>
</body>
</html>
+369
View File
@@ -0,0 +1,369 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.5">
<title>connpy.cli.run_handler API documentation</title>
<meta name="description" content="">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source > summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible;min-width:max-content}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin:1em 0}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
/* Collapse source docstrings */
setTimeout(() => {
[...document.querySelectorAll('.hljs.language-python > .hljs-string')]
.filter(el => el.innerHTML.length > 200 && ['"""', "'''"].includes(el.innerHTML.substring(0, 3)))
.forEach(el => {
let d = document.createElement('details');
d.classList.add('hljs-string');
d.innerHTML = '<summary>"""</summary>' + el.innerHTML.substring(3);
el.replaceWith(d);
});
}, 100);
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>connpy.cli.run_handler</code></h1>
</header>
<section id="section-intro">
</section>
<section>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="connpy.cli.run_handler.RunHandler"><code class="flex name class">
<span>class <span class="ident">RunHandler</span></span>
<span>(</span><span>app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class RunHandler:
def __init__(self, app):
self.app = app
def dispatch(self, args):
if len(args.data) &gt; 1:
args.action = &#34;noderun&#34;
actions = {&#34;noderun&#34;: self.node_run, &#34;generate&#34;: self.yaml_generate, &#34;run&#34;: self.yaml_run}
return actions.get(args.action)(args)
def node_run(self, args):
nodes_filter = args.data[0]
commands = [&#34; &#34;.join(args.data[1:])]
try:
header_printed = False
# Inline execution with streaming results
def _on_node_complete(unique, node_output, node_status):
nonlocal header_printed
if not header_printed:
printer.console.print(Rule(&#34;OUTPUT&#34;, style=&#34;header&#34;))
header_printed = True
printer.node_panel(unique, node_output, node_status)
self.app.services.execution.run_commands(
nodes_filter=nodes_filter,
commands=commands,
on_node_complete=_on_node_complete
)
except ConnpyError as e:
printer.error(str(e))
sys.exit(1)
def yaml_generate(self, args):
if os.path.exists(args.data[0]):
printer.error(f&#34;File &#39;{args.data[0]}&#39; already exists.&#34;)
sys.exit(14)
else:
with open(args.data[0], &#34;w&#34;) as file:
file.write(get_instructions(&#34;generate&#34;))
printer.success(f&#34;File {args.data[0]} generated successfully&#34;)
sys.exit()
def yaml_run(self, args):
path = args.data[0]
try:
with open(path, &#34;r&#34;) as f:
playbook = yaml.load(f, Loader=yaml.FullLoader)
for task in playbook.get(&#34;tasks&#34;, []):
self.cli_run(task)
except Exception as e:
printer.error(f&#34;Failed to run playbook {path}: {e}&#34;)
sys.exit(10)
def cli_run(self, script):
try:
action = script[&#34;action&#34;]
nodelist = script[&#34;nodes&#34;]
commands = script[&#34;commands&#34;]
variables = script.get(&#34;variables&#34;)
output_cfg = script[&#34;output&#34;]
name = script.get(&#34;name&#34;, &#34;Task&#34;)
options = script.get(&#34;options&#34;, {})
except KeyError as e:
printer.error(f&#34;&#39;{e.args[0]}&#39; is mandatory in script&#34;)
sys.exit(11)
stdout = (output_cfg == &#34;stdout&#34;)
folder = output_cfg if output_cfg not in [None, &#34;stdout&#34;] else None
prompt = options.get(&#34;prompt&#34;)
printer.header(name.upper())
try:
if action == &#34;run&#34;:
# If stdout is true, we stream results as they arrive
on_complete = printer.node_panel if stdout else None
results = self.app.services.execution.run_commands(
nodes_filter=nodelist,
commands=commands,
variables=variables,
parallel=options.get(&#34;parallel&#34;, 10),
timeout=options.get(&#34;timeout&#34;, 10),
folder=folder,
prompt=prompt,
on_node_complete=on_complete
)
# If not streaming, we could print a summary table here if needed
if not stdout:
for unique, output in results.items():
printer.node_panel(unique, output, 0)
elif action == &#34;test&#34;:
expected = script.get(&#34;expected&#34;, [])
on_complete = printer.test_panel if stdout else None
results = self.app.services.execution.test_commands(
nodes_filter=nodelist,
commands=commands,
expected=expected,
variables=variables,
parallel=options.get(&#34;parallel&#34;, 10),
timeout=options.get(&#34;timeout&#34;, 10),
prompt=prompt,
on_node_complete=on_complete
)
if not stdout:
printer.test_summary(results)
except ConnpyError as e:
printer.error(str(e))</code></pre>
</details>
<div class="desc"></div>
<h3>Methods</h3>
<dl>
<dt id="connpy.cli.run_handler.RunHandler.cli_run"><code class="name flex">
<span>def <span class="ident">cli_run</span></span>(<span>self, script)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def cli_run(self, script):
try:
action = script[&#34;action&#34;]
nodelist = script[&#34;nodes&#34;]
commands = script[&#34;commands&#34;]
variables = script.get(&#34;variables&#34;)
output_cfg = script[&#34;output&#34;]
name = script.get(&#34;name&#34;, &#34;Task&#34;)
options = script.get(&#34;options&#34;, {})
except KeyError as e:
printer.error(f&#34;&#39;{e.args[0]}&#39; is mandatory in script&#34;)
sys.exit(11)
stdout = (output_cfg == &#34;stdout&#34;)
folder = output_cfg if output_cfg not in [None, &#34;stdout&#34;] else None
prompt = options.get(&#34;prompt&#34;)
printer.header(name.upper())
try:
if action == &#34;run&#34;:
# If stdout is true, we stream results as they arrive
on_complete = printer.node_panel if stdout else None
results = self.app.services.execution.run_commands(
nodes_filter=nodelist,
commands=commands,
variables=variables,
parallel=options.get(&#34;parallel&#34;, 10),
timeout=options.get(&#34;timeout&#34;, 10),
folder=folder,
prompt=prompt,
on_node_complete=on_complete
)
# If not streaming, we could print a summary table here if needed
if not stdout:
for unique, output in results.items():
printer.node_panel(unique, output, 0)
elif action == &#34;test&#34;:
expected = script.get(&#34;expected&#34;, [])
on_complete = printer.test_panel if stdout else None
results = self.app.services.execution.test_commands(
nodes_filter=nodelist,
commands=commands,
expected=expected,
variables=variables,
parallel=options.get(&#34;parallel&#34;, 10),
timeout=options.get(&#34;timeout&#34;, 10),
prompt=prompt,
on_node_complete=on_complete
)
if not stdout:
printer.test_summary(results)
except ConnpyError as e:
printer.error(str(e))</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.run_handler.RunHandler.dispatch"><code class="name flex">
<span>def <span class="ident">dispatch</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def dispatch(self, args):
if len(args.data) &gt; 1:
args.action = &#34;noderun&#34;
actions = {&#34;noderun&#34;: self.node_run, &#34;generate&#34;: self.yaml_generate, &#34;run&#34;: self.yaml_run}
return actions.get(args.action)(args)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.run_handler.RunHandler.node_run"><code class="name flex">
<span>def <span class="ident">node_run</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def node_run(self, args):
nodes_filter = args.data[0]
commands = [&#34; &#34;.join(args.data[1:])]
try:
header_printed = False
# Inline execution with streaming results
def _on_node_complete(unique, node_output, node_status):
nonlocal header_printed
if not header_printed:
printer.console.print(Rule(&#34;OUTPUT&#34;, style=&#34;header&#34;))
header_printed = True
printer.node_panel(unique, node_output, node_status)
self.app.services.execution.run_commands(
nodes_filter=nodes_filter,
commands=commands,
on_node_complete=_on_node_complete
)
except ConnpyError as e:
printer.error(str(e))
sys.exit(1)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.run_handler.RunHandler.yaml_generate"><code class="name flex">
<span>def <span class="ident">yaml_generate</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def yaml_generate(self, args):
if os.path.exists(args.data[0]):
printer.error(f&#34;File &#39;{args.data[0]}&#39; already exists.&#34;)
sys.exit(14)
else:
with open(args.data[0], &#34;w&#34;) as file:
file.write(get_instructions(&#34;generate&#34;))
printer.success(f&#34;File {args.data[0]} generated successfully&#34;)
sys.exit()</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.run_handler.RunHandler.yaml_run"><code class="name flex">
<span>def <span class="ident">yaml_run</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def yaml_run(self, args):
path = args.data[0]
try:
with open(path, &#34;r&#34;) as f:
playbook = yaml.load(f, Loader=yaml.FullLoader)
for task in playbook.get(&#34;tasks&#34;, []):
self.cli_run(task)
except Exception as e:
printer.error(f&#34;Failed to run playbook {path}: {e}&#34;)
sys.exit(10)</code></pre>
</details>
<div class="desc"></div>
</dd>
</dl>
</dd>
</dl>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="connpy.cli" href="index.html">connpy.cli</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="connpy.cli.run_handler.RunHandler" href="#connpy.cli.run_handler.RunHandler">RunHandler</a></code></h4>
<ul class="">
<li><code><a title="connpy.cli.run_handler.RunHandler.cli_run" href="#connpy.cli.run_handler.RunHandler.cli_run">cli_run</a></code></li>
<li><code><a title="connpy.cli.run_handler.RunHandler.dispatch" href="#connpy.cli.run_handler.RunHandler.dispatch">dispatch</a></code></li>
<li><code><a title="connpy.cli.run_handler.RunHandler.node_run" href="#connpy.cli.run_handler.RunHandler.node_run">node_run</a></code></li>
<li><code><a title="connpy.cli.run_handler.RunHandler.yaml_generate" href="#connpy.cli.run_handler.RunHandler.yaml_generate">yaml_generate</a></code></li>
<li><code><a title="connpy.cli.run_handler.RunHandler.yaml_run" href="#connpy.cli.run_handler.RunHandler.yaml_run">yaml_run</a></code></li>
</ul>
</li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.5</a>.</p>
</footer>
</body>
</html>
+433
View File
@@ -0,0 +1,433 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.5">
<title>connpy.cli.sync_handler API documentation</title>
<meta name="description" content="">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source > summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible;min-width:max-content}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin:1em 0}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
/* Collapse source docstrings */
setTimeout(() => {
[...document.querySelectorAll('.hljs.language-python > .hljs-string')]
.filter(el => el.innerHTML.length > 200 && ['"""', "'''"].includes(el.innerHTML.substring(0, 3)))
.forEach(el => {
let d = document.createElement('details');
d.classList.add('hljs-string');
d.innerHTML = '<summary>"""</summary>' + el.innerHTML.substring(3);
el.replaceWith(d);
});
}, 100);
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>connpy.cli.sync_handler</code></h1>
</header>
<section id="section-intro">
</section>
<section>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="connpy.cli.sync_handler.SyncHandler"><code class="flex name class">
<span>class <span class="ident">SyncHandler</span></span>
<span>(</span><span>app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class SyncHandler:
def __init__(self, app):
self.app = app
def dispatch(self, args):
action = getattr(args, &#34;action&#34;, None)
actions = {
&#34;login&#34;: self.login,
&#34;logout&#34;: self.logout,
&#34;status&#34;: self.status,
&#34;list&#34;: self.list_backups,
&#34;once&#34;: self.once,
&#34;restore&#34;: self.restore,
&#34;start&#34;: self.start,
&#34;stop&#34;: self.stop
}
handler = actions.get(action)
if handler:
return handler(args)
return self.status(args)
def login(self, args):
self.app.services.sync.login()
def logout(self, args):
self.app.services.sync.logout()
def status(self, args):
status = self.app.services.sync.check_login_status()
enabled = self.app.services.sync.sync_enabled
remote = self.app.services.sync.sync_remote
printer.info(f&#34;Login Status: {status}&#34;)
printer.info(f&#34;Auto-Sync: {&#39;Enabled&#39; if enabled else &#39;Disabled&#39;}&#34;)
printer.info(f&#34;Sync Remote Nodes: {&#39;Yes&#39; if remote else &#39;No&#39;}&#34;)
def list_backups(self, args):
backups = self.app.services.sync.list_backups()
if backups:
yaml_output = yaml.dump(backups, sort_keys=False, default_flow_style=False)
printer.custom(&#34;backups&#34;, &#34;&#34;)
print(yaml_output)
else:
printer.info(&#34;No backups found or not logged in.&#34;)
def once(self, args):
# Manual backup. We check if we should include remote nodes
remote_data = None
if self.app.services.sync.sync_remote and self.app.services.mode == &#34;remote&#34;:
inventory = self.app.services.nodes.get_inventory()
# Merge with local settings
local_settings = self.app.services.config_svc.get_settings()
local_settings.pop(&#34;configfolder&#34;, None)
# Maintain proper config structure: {config: {}, connections: {}, profiles: {}}
remote_data = {
&#34;config&#34;: local_settings,
&#34;connections&#34;: inventory.get(&#34;connections&#34;, {}),
&#34;profiles&#34;: inventory.get(&#34;profiles&#34;, {})
}
if self.app.services.sync.compress_and_upload(remote_data):
printer.success(&#34;Manual backup completed.&#34;)
def restore(self, args):
import inquirer
file_id = getattr(args, &#34;id&#34;, None)
# Segmented flags
restore_config = getattr(args, &#34;restore_config&#34;, False)
restore_nodes = getattr(args, &#34;restore_nodes&#34;, False)
# If neither is specified, we restore ALL (backwards compatibility)
if not restore_config and not restore_nodes:
restore_config = True
restore_nodes = True
# 1. Analyze what we are about to restore
info = self.app.services.sync.analyze_backup_content(file_id)
if not info:
printer.error(&#34;Could not analyze backup content.&#34;)
return
# 2. Show detailed info
printer.info(&#34;Restoration Details:&#34;)
if restore_config:
print(f&#34; - Local Settings: Yes&#34;)
print(f&#34; - RSA Key (.osk): {&#39;Yes&#39; if info[&#39;has_key&#39;] else &#39;No&#39;}&#34;)
if restore_nodes:
target = &#34;REMOTE&#34; if self.app.services.mode == &#34;remote&#34; else &#34;LOCAL&#34;
print(f&#34; - Nodes: {info[&#39;nodes&#39;]}&#34;)
print(f&#34; - Folders: {info[&#39;folders&#39;]}&#34;)
print(f&#34; - Profiles: {info[&#39;profiles&#39;]}&#34;)
print(f&#34; - Destination: {target}&#34;)
print(&#34;&#34;)
questions = [inquirer.Confirm(&#34;confirm&#34;, message=&#34;Do you want to proceed with the restoration?&#34;, default=False)]
answers = inquirer.prompt(questions)
if not answers or not answers[&#34;confirm&#34;]:
printer.info(&#34;Restore cancelled.&#34;)
return
# 3. Perform the actual restore
if self.app.services.sync.restore_backup(
file_id=file_id,
restore_config=restore_config,
restore_nodes=restore_nodes,
app_instance=self.app
):
printer.success(&#34;Restore completed successfully.&#34;)
def start(self, args):
self.app.services.config_svc.update_setting(&#34;sync&#34;, True)
self.app.services.sync.sync_enabled = True
printer.success(&#34;Auto-sync enabled.&#34;)
def stop(self, args):
self.app.services.config_svc.update_setting(&#34;sync&#34;, False)
self.app.services.sync.sync_enabled = False
printer.success(&#34;Auto-sync disabled.&#34;)</code></pre>
</details>
<div class="desc"></div>
<h3>Methods</h3>
<dl>
<dt id="connpy.cli.sync_handler.SyncHandler.dispatch"><code class="name flex">
<span>def <span class="ident">dispatch</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def dispatch(self, args):
action = getattr(args, &#34;action&#34;, None)
actions = {
&#34;login&#34;: self.login,
&#34;logout&#34;: self.logout,
&#34;status&#34;: self.status,
&#34;list&#34;: self.list_backups,
&#34;once&#34;: self.once,
&#34;restore&#34;: self.restore,
&#34;start&#34;: self.start,
&#34;stop&#34;: self.stop
}
handler = actions.get(action)
if handler:
return handler(args)
return self.status(args)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.sync_handler.SyncHandler.list_backups"><code class="name flex">
<span>def <span class="ident">list_backups</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def list_backups(self, args):
backups = self.app.services.sync.list_backups()
if backups:
yaml_output = yaml.dump(backups, sort_keys=False, default_flow_style=False)
printer.custom(&#34;backups&#34;, &#34;&#34;)
print(yaml_output)
else:
printer.info(&#34;No backups found or not logged in.&#34;)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.sync_handler.SyncHandler.login"><code class="name flex">
<span>def <span class="ident">login</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def login(self, args):
self.app.services.sync.login()</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.sync_handler.SyncHandler.logout"><code class="name flex">
<span>def <span class="ident">logout</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def logout(self, args):
self.app.services.sync.logout()</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.sync_handler.SyncHandler.once"><code class="name flex">
<span>def <span class="ident">once</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def once(self, args):
# Manual backup. We check if we should include remote nodes
remote_data = None
if self.app.services.sync.sync_remote and self.app.services.mode == &#34;remote&#34;:
inventory = self.app.services.nodes.get_inventory()
# Merge with local settings
local_settings = self.app.services.config_svc.get_settings()
local_settings.pop(&#34;configfolder&#34;, None)
# Maintain proper config structure: {config: {}, connections: {}, profiles: {}}
remote_data = {
&#34;config&#34;: local_settings,
&#34;connections&#34;: inventory.get(&#34;connections&#34;, {}),
&#34;profiles&#34;: inventory.get(&#34;profiles&#34;, {})
}
if self.app.services.sync.compress_and_upload(remote_data):
printer.success(&#34;Manual backup completed.&#34;)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.sync_handler.SyncHandler.restore"><code class="name flex">
<span>def <span class="ident">restore</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def restore(self, args):
import inquirer
file_id = getattr(args, &#34;id&#34;, None)
# Segmented flags
restore_config = getattr(args, &#34;restore_config&#34;, False)
restore_nodes = getattr(args, &#34;restore_nodes&#34;, False)
# If neither is specified, we restore ALL (backwards compatibility)
if not restore_config and not restore_nodes:
restore_config = True
restore_nodes = True
# 1. Analyze what we are about to restore
info = self.app.services.sync.analyze_backup_content(file_id)
if not info:
printer.error(&#34;Could not analyze backup content.&#34;)
return
# 2. Show detailed info
printer.info(&#34;Restoration Details:&#34;)
if restore_config:
print(f&#34; - Local Settings: Yes&#34;)
print(f&#34; - RSA Key (.osk): {&#39;Yes&#39; if info[&#39;has_key&#39;] else &#39;No&#39;}&#34;)
if restore_nodes:
target = &#34;REMOTE&#34; if self.app.services.mode == &#34;remote&#34; else &#34;LOCAL&#34;
print(f&#34; - Nodes: {info[&#39;nodes&#39;]}&#34;)
print(f&#34; - Folders: {info[&#39;folders&#39;]}&#34;)
print(f&#34; - Profiles: {info[&#39;profiles&#39;]}&#34;)
print(f&#34; - Destination: {target}&#34;)
print(&#34;&#34;)
questions = [inquirer.Confirm(&#34;confirm&#34;, message=&#34;Do you want to proceed with the restoration?&#34;, default=False)]
answers = inquirer.prompt(questions)
if not answers or not answers[&#34;confirm&#34;]:
printer.info(&#34;Restore cancelled.&#34;)
return
# 3. Perform the actual restore
if self.app.services.sync.restore_backup(
file_id=file_id,
restore_config=restore_config,
restore_nodes=restore_nodes,
app_instance=self.app
):
printer.success(&#34;Restore completed successfully.&#34;)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.sync_handler.SyncHandler.start"><code class="name flex">
<span>def <span class="ident">start</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def start(self, args):
self.app.services.config_svc.update_setting(&#34;sync&#34;, True)
self.app.services.sync.sync_enabled = True
printer.success(&#34;Auto-sync enabled.&#34;)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.sync_handler.SyncHandler.status"><code class="name flex">
<span>def <span class="ident">status</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def status(self, args):
status = self.app.services.sync.check_login_status()
enabled = self.app.services.sync.sync_enabled
remote = self.app.services.sync.sync_remote
printer.info(f&#34;Login Status: {status}&#34;)
printer.info(f&#34;Auto-Sync: {&#39;Enabled&#39; if enabled else &#39;Disabled&#39;}&#34;)
printer.info(f&#34;Sync Remote Nodes: {&#39;Yes&#39; if remote else &#39;No&#39;}&#34;)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.sync_handler.SyncHandler.stop"><code class="name flex">
<span>def <span class="ident">stop</span></span>(<span>self, args)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def stop(self, args):
self.app.services.config_svc.update_setting(&#34;sync&#34;, False)
self.app.services.sync.sync_enabled = False
printer.success(&#34;Auto-sync disabled.&#34;)</code></pre>
</details>
<div class="desc"></div>
</dd>
</dl>
</dd>
</dl>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="connpy.cli" href="index.html">connpy.cli</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="connpy.cli.sync_handler.SyncHandler" href="#connpy.cli.sync_handler.SyncHandler">SyncHandler</a></code></h4>
<ul class="two-column">
<li><code><a title="connpy.cli.sync_handler.SyncHandler.dispatch" href="#connpy.cli.sync_handler.SyncHandler.dispatch">dispatch</a></code></li>
<li><code><a title="connpy.cli.sync_handler.SyncHandler.list_backups" href="#connpy.cli.sync_handler.SyncHandler.list_backups">list_backups</a></code></li>
<li><code><a title="connpy.cli.sync_handler.SyncHandler.login" href="#connpy.cli.sync_handler.SyncHandler.login">login</a></code></li>
<li><code><a title="connpy.cli.sync_handler.SyncHandler.logout" href="#connpy.cli.sync_handler.SyncHandler.logout">logout</a></code></li>
<li><code><a title="connpy.cli.sync_handler.SyncHandler.once" href="#connpy.cli.sync_handler.SyncHandler.once">once</a></code></li>
<li><code><a title="connpy.cli.sync_handler.SyncHandler.restore" href="#connpy.cli.sync_handler.SyncHandler.restore">restore</a></code></li>
<li><code><a title="connpy.cli.sync_handler.SyncHandler.start" href="#connpy.cli.sync_handler.SyncHandler.start">start</a></code></li>
<li><code><a title="connpy.cli.sync_handler.SyncHandler.status" href="#connpy.cli.sync_handler.SyncHandler.status">status</a></code></li>
<li><code><a title="connpy.cli.sync_handler.SyncHandler.stop" href="#connpy.cli.sync_handler.SyncHandler.stop">stop</a></code></li>
</ul>
</li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.5</a>.</p>
</footer>
</body>
</html>
+514
View File
@@ -0,0 +1,514 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.5">
<title>connpy.cli.validators API documentation</title>
<meta name="description" content="">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source > summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible;min-width:max-content}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin:1em 0}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
/* Collapse source docstrings */
setTimeout(() => {
[...document.querySelectorAll('.hljs.language-python > .hljs-string')]
.filter(el => el.innerHTML.length > 200 && ['"""', "'''"].includes(el.innerHTML.substring(0, 3)))
.forEach(el => {
let d = document.createElement('details');
d.classList.add('hljs-string');
d.innerHTML = '<summary>"""</summary>' + el.innerHTML.substring(3);
el.replaceWith(d);
});
}, 100);
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>connpy.cli.validators</code></h1>
</header>
<section id="section-intro">
</section>
<section>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="connpy.cli.validators.Validators"><code class="flex name class">
<span>class <span class="ident">Validators</span></span>
<span>(</span><span>app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class Validators:
def __init__(self, app):
self.app = app
def host_validation(self, answers, current, regex = &#34;^.+$&#34;):
if not re.match(regex, current):
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Host cannot be empty&#34;)
if current.startswith(&#34;@&#34;):
if current[1:] not in self.app.profiles:
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Profile {} don&#39;t exist&#34;.format(current))
return True
def profile_protocol_validation(self, answers, current, regex = &#34;(^ssh$|^telnet$|^kubectl$|^docker$|^$)&#34;):
if not re.match(regex, current):
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Pick between ssh, telnet, kubectl, docker or leave empty&#34;)
return True
def protocol_validation(self, answers, current, regex = &#34;(^ssh$|^telnet$|^kubectl$|^docker$|^$|^@.+$)&#34;):
if not re.match(regex, current):
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Pick between ssh, telnet, kubectl, docker leave empty or @profile&#34;)
if current.startswith(&#34;@&#34;):
if current[1:] not in self.app.profiles:
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Profile {} don&#39;t exist&#34;.format(current))
return True
def profile_port_validation(self, answers, current, regex = &#34;(^[0-9]*$)&#34;):
if not re.match(regex, current):
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Pick a port between 1-65535, @profile o leave empty&#34;)
try:
port = int(current)
except ValueError:
port = 0
if current != &#34;&#34; and not 1 &lt;= int(port) &lt;= 65535:
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Pick a port between 1-65535 or leave empty&#34;)
return True
def port_validation(self, answers, current, regex = &#34;(^[0-9]*$|^@.+$)&#34;):
if not re.match(regex, current):
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Pick a port between 1-65535, @profile or leave empty&#34;)
try:
port = int(current)
except ValueError:
port = 0
if current.startswith(&#34;@&#34;):
if current[1:] not in self.app.profiles:
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Profile {} don&#39;t exist&#34;.format(current))
elif current != &#34;&#34; and not 1 &lt;= int(port) &lt;= 65535:
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Pick a port between 1-65535, @profile o leave empty&#34;)
return True
def pass_validation(self, answers, current, regex = &#34;(^@.+$)&#34;):
profiles = current.split(&#34;,&#34;)
for i in profiles:
if not re.match(regex, i) or i[1:] not in self.app.profiles:
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Profile {} don&#39;t exist&#34;.format(i))
return True
def tags_validation(self, answers, current):
if current.startswith(&#34;@&#34;):
if current[1:] not in self.app.profiles:
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Profile {} don&#39;t exist&#34;.format(current))
elif current != &#34;&#34;:
isdict = False
try:
isdict = ast.literal_eval(current)
except Exception:
pass
if not isinstance (isdict, dict):
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Tags should be a python dictionary.&#34;.format(current))
return True
def profile_tags_validation(self, answers, current):
if current != &#34;&#34;:
isdict = False
try:
isdict = ast.literal_eval(current)
except Exception:
pass
if not isinstance (isdict, dict):
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Tags should be a python dictionary.&#34;.format(current))
return True
def jumphost_validation(self, answers, current):
if current.startswith(&#34;@&#34;):
if current[1:] not in self.app.profiles:
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Profile {} don&#39;t exist&#34;.format(current))
elif current != &#34;&#34;:
if current not in self.app.nodes_list:
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Node {} don&#39;t exist.&#34;.format(current))
return True
def profile_jumphost_validation(self, answers, current):
if current != &#34;&#34;:
if current not in self.app.nodes_list:
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Node {} don&#39;t exist.&#34;.format(current))
return True
def default_validation(self, answers, current):
if current.startswith(&#34;@&#34;):
if current[1:] not in self.app.profiles:
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Profile {} don&#39;t exist&#34;.format(current))
return True
def bulk_node_validation(self, answers, current, regex = &#34;^[0-9a-zA-Z_.,$#-]+$&#34;):
if not re.match(regex, current):
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Host cannot be empty&#34;)
if current.startswith(&#34;@&#34;):
if current[1:] not in self.app.profiles:
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Profile {} don&#39;t exist&#34;.format(current))
return True
def bulk_folder_validation(self, answers, current):
if not self.app.case:
current = current.lower()
candidate = current
if &#34;/&#34; in current:
candidate = current.split(&#34;/&#34;)[0]
matches = list(filter(lambda k: k == candidate, self.app.folders))
if current != &#34;&#34; and len(matches) == 0:
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Location {} don&#39;t exist&#34;.format(current))
return True
def bulk_host_validation(self, answers, current, regex = &#34;^.+$&#34;):
if not re.match(regex, current):
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Host cannot be empty&#34;)
if current.startswith(&#34;@&#34;):
if current[1:] not in self.app.profiles:
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Profile {} don&#39;t exist&#34;.format(current))
hosts = current.split(&#34;,&#34;)
nodes = answers[&#34;ids&#34;].split(&#34;,&#34;)
if len(hosts) &gt; 1 and len(hosts) != len(nodes):
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Hosts list should be the same length of nodes list&#34;)
return True</code></pre>
</details>
<div class="desc"></div>
<h3>Methods</h3>
<dl>
<dt id="connpy.cli.validators.Validators.bulk_folder_validation"><code class="name flex">
<span>def <span class="ident">bulk_folder_validation</span></span>(<span>self, answers, current)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def bulk_folder_validation(self, answers, current):
if not self.app.case:
current = current.lower()
candidate = current
if &#34;/&#34; in current:
candidate = current.split(&#34;/&#34;)[0]
matches = list(filter(lambda k: k == candidate, self.app.folders))
if current != &#34;&#34; and len(matches) == 0:
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Location {} don&#39;t exist&#34;.format(current))
return True</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.validators.Validators.bulk_host_validation"><code class="name flex">
<span>def <span class="ident">bulk_host_validation</span></span>(<span>self, answers, current, regex='^.+$')</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def bulk_host_validation(self, answers, current, regex = &#34;^.+$&#34;):
if not re.match(regex, current):
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Host cannot be empty&#34;)
if current.startswith(&#34;@&#34;):
if current[1:] not in self.app.profiles:
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Profile {} don&#39;t exist&#34;.format(current))
hosts = current.split(&#34;,&#34;)
nodes = answers[&#34;ids&#34;].split(&#34;,&#34;)
if len(hosts) &gt; 1 and len(hosts) != len(nodes):
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Hosts list should be the same length of nodes list&#34;)
return True</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.validators.Validators.bulk_node_validation"><code class="name flex">
<span>def <span class="ident">bulk_node_validation</span></span>(<span>self, answers, current, regex='^[0-9a-zA-Z_.,$#-]+$')</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def bulk_node_validation(self, answers, current, regex = &#34;^[0-9a-zA-Z_.,$#-]+$&#34;):
if not re.match(regex, current):
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Host cannot be empty&#34;)
if current.startswith(&#34;@&#34;):
if current[1:] not in self.app.profiles:
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Profile {} don&#39;t exist&#34;.format(current))
return True</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.validators.Validators.default_validation"><code class="name flex">
<span>def <span class="ident">default_validation</span></span>(<span>self, answers, current)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def default_validation(self, answers, current):
if current.startswith(&#34;@&#34;):
if current[1:] not in self.app.profiles:
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Profile {} don&#39;t exist&#34;.format(current))
return True</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.validators.Validators.host_validation"><code class="name flex">
<span>def <span class="ident">host_validation</span></span>(<span>self, answers, current, regex='^.+$')</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def host_validation(self, answers, current, regex = &#34;^.+$&#34;):
if not re.match(regex, current):
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Host cannot be empty&#34;)
if current.startswith(&#34;@&#34;):
if current[1:] not in self.app.profiles:
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Profile {} don&#39;t exist&#34;.format(current))
return True</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.validators.Validators.jumphost_validation"><code class="name flex">
<span>def <span class="ident">jumphost_validation</span></span>(<span>self, answers, current)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def jumphost_validation(self, answers, current):
if current.startswith(&#34;@&#34;):
if current[1:] not in self.app.profiles:
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Profile {} don&#39;t exist&#34;.format(current))
elif current != &#34;&#34;:
if current not in self.app.nodes_list:
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Node {} don&#39;t exist.&#34;.format(current))
return True</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.validators.Validators.pass_validation"><code class="name flex">
<span>def <span class="ident">pass_validation</span></span>(<span>self, answers, current, regex='(^@.+$)')</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def pass_validation(self, answers, current, regex = &#34;(^@.+$)&#34;):
profiles = current.split(&#34;,&#34;)
for i in profiles:
if not re.match(regex, i) or i[1:] not in self.app.profiles:
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Profile {} don&#39;t exist&#34;.format(i))
return True</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.validators.Validators.port_validation"><code class="name flex">
<span>def <span class="ident">port_validation</span></span>(<span>self, answers, current, regex='(^[0-9]*$|^@.+$)')</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def port_validation(self, answers, current, regex = &#34;(^[0-9]*$|^@.+$)&#34;):
if not re.match(regex, current):
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Pick a port between 1-65535, @profile or leave empty&#34;)
try:
port = int(current)
except ValueError:
port = 0
if current.startswith(&#34;@&#34;):
if current[1:] not in self.app.profiles:
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Profile {} don&#39;t exist&#34;.format(current))
elif current != &#34;&#34; and not 1 &lt;= int(port) &lt;= 65535:
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Pick a port between 1-65535, @profile o leave empty&#34;)
return True</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.validators.Validators.profile_jumphost_validation"><code class="name flex">
<span>def <span class="ident">profile_jumphost_validation</span></span>(<span>self, answers, current)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def profile_jumphost_validation(self, answers, current):
if current != &#34;&#34;:
if current not in self.app.nodes_list:
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Node {} don&#39;t exist.&#34;.format(current))
return True</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.validators.Validators.profile_port_validation"><code class="name flex">
<span>def <span class="ident">profile_port_validation</span></span>(<span>self, answers, current, regex='(^[0-9]*$)')</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def profile_port_validation(self, answers, current, regex = &#34;(^[0-9]*$)&#34;):
if not re.match(regex, current):
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Pick a port between 1-65535, @profile o leave empty&#34;)
try:
port = int(current)
except ValueError:
port = 0
if current != &#34;&#34; and not 1 &lt;= int(port) &lt;= 65535:
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Pick a port between 1-65535 or leave empty&#34;)
return True</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.validators.Validators.profile_protocol_validation"><code class="name flex">
<span>def <span class="ident">profile_protocol_validation</span></span>(<span>self, answers, current, regex='(^ssh$|^telnet$|^kubectl$|^docker$|^$)')</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def profile_protocol_validation(self, answers, current, regex = &#34;(^ssh$|^telnet$|^kubectl$|^docker$|^$)&#34;):
if not re.match(regex, current):
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Pick between ssh, telnet, kubectl, docker or leave empty&#34;)
return True</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.validators.Validators.profile_tags_validation"><code class="name flex">
<span>def <span class="ident">profile_tags_validation</span></span>(<span>self, answers, current)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def profile_tags_validation(self, answers, current):
if current != &#34;&#34;:
isdict = False
try:
isdict = ast.literal_eval(current)
except Exception:
pass
if not isinstance (isdict, dict):
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Tags should be a python dictionary.&#34;.format(current))
return True</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.validators.Validators.protocol_validation"><code class="name flex">
<span>def <span class="ident">protocol_validation</span></span>(<span>self, answers, current, regex='(^ssh$|^telnet$|^kubectl$|^docker$|^$|^@.+$)')</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def protocol_validation(self, answers, current, regex = &#34;(^ssh$|^telnet$|^kubectl$|^docker$|^$|^@.+$)&#34;):
if not re.match(regex, current):
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Pick between ssh, telnet, kubectl, docker leave empty or @profile&#34;)
if current.startswith(&#34;@&#34;):
if current[1:] not in self.app.profiles:
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Profile {} don&#39;t exist&#34;.format(current))
return True</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.cli.validators.Validators.tags_validation"><code class="name flex">
<span>def <span class="ident">tags_validation</span></span>(<span>self, answers, current)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def tags_validation(self, answers, current):
if current.startswith(&#34;@&#34;):
if current[1:] not in self.app.profiles:
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Profile {} don&#39;t exist&#34;.format(current))
elif current != &#34;&#34;:
isdict = False
try:
isdict = ast.literal_eval(current)
except Exception:
pass
if not isinstance (isdict, dict):
raise inquirer.errors.ValidationError(&#34;&#34;, reason=&#34;Tags should be a python dictionary.&#34;.format(current))
return True</code></pre>
</details>
<div class="desc"></div>
</dd>
</dl>
</dd>
</dl>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="connpy.cli" href="index.html">connpy.cli</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="connpy.cli.validators.Validators" href="#connpy.cli.validators.Validators">Validators</a></code></h4>
<ul class="">
<li><code><a title="connpy.cli.validators.Validators.bulk_folder_validation" href="#connpy.cli.validators.Validators.bulk_folder_validation">bulk_folder_validation</a></code></li>
<li><code><a title="connpy.cli.validators.Validators.bulk_host_validation" href="#connpy.cli.validators.Validators.bulk_host_validation">bulk_host_validation</a></code></li>
<li><code><a title="connpy.cli.validators.Validators.bulk_node_validation" href="#connpy.cli.validators.Validators.bulk_node_validation">bulk_node_validation</a></code></li>
<li><code><a title="connpy.cli.validators.Validators.default_validation" href="#connpy.cli.validators.Validators.default_validation">default_validation</a></code></li>
<li><code><a title="connpy.cli.validators.Validators.host_validation" href="#connpy.cli.validators.Validators.host_validation">host_validation</a></code></li>
<li><code><a title="connpy.cli.validators.Validators.jumphost_validation" href="#connpy.cli.validators.Validators.jumphost_validation">jumphost_validation</a></code></li>
<li><code><a title="connpy.cli.validators.Validators.pass_validation" href="#connpy.cli.validators.Validators.pass_validation">pass_validation</a></code></li>
<li><code><a title="connpy.cli.validators.Validators.port_validation" href="#connpy.cli.validators.Validators.port_validation">port_validation</a></code></li>
<li><code><a title="connpy.cli.validators.Validators.profile_jumphost_validation" href="#connpy.cli.validators.Validators.profile_jumphost_validation">profile_jumphost_validation</a></code></li>
<li><code><a title="connpy.cli.validators.Validators.profile_port_validation" href="#connpy.cli.validators.Validators.profile_port_validation">profile_port_validation</a></code></li>
<li><code><a title="connpy.cli.validators.Validators.profile_protocol_validation" href="#connpy.cli.validators.Validators.profile_protocol_validation">profile_protocol_validation</a></code></li>
<li><code><a title="connpy.cli.validators.Validators.profile_tags_validation" href="#connpy.cli.validators.Validators.profile_tags_validation">profile_tags_validation</a></code></li>
<li><code><a title="connpy.cli.validators.Validators.protocol_validation" href="#connpy.cli.validators.Validators.protocol_validation">protocol_validation</a></code></li>
<li><code><a title="connpy.cli.validators.Validators.tags_validation" href="#connpy.cli.validators.Validators.tags_validation">tags_validation</a></code></li>
</ul>
</li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.5</a>.</p>
</footer>
</body>
</html>
+799
View File
@@ -0,0 +1,799 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.5">
<title>connpy.grpc.connpy_pb2 API documentation</title>
<meta name="description" content="Generated protocol buffer code.">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source > summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible;min-width:max-content}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin:1em 0}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
/* Collapse source docstrings */
setTimeout(() => {
[...document.querySelectorAll('.hljs.language-python > .hljs-string')]
.filter(el => el.innerHTML.length > 200 && ['"""', "'''"].includes(el.innerHTML.substring(0, 3)))
.forEach(el => {
let d = document.createElement('details');
d.classList.add('hljs-string');
d.innerHTML = '<summary>"""</summary>' + el.innerHTML.substring(3);
el.replaceWith(d);
});
}, 100);
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>connpy.grpc.connpy_pb2</code></h1>
</header>
<section id="section-intro">
<p>Generated protocol buffer code.</p>
</section>
<section>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="connpy.grpc.connpy_pb2.AIResponse"><code class="flex name class">
<span>class <span class="ident">AIResponse</span></span>
<span>(</span><span>*args, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>A ProtocolMessage</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li>google._upb._message.Message</li>
<li>google.protobuf.message.Message</li>
</ul>
<h3>Class variables</h3>
<dl>
<dt id="connpy.grpc.connpy_pb2.AIResponse.DESCRIPTOR"><code class="name">var <span class="ident">DESCRIPTOR</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
</dd>
<dt id="connpy.grpc.connpy_pb2.AskRequest"><code class="flex name class">
<span>class <span class="ident">AskRequest</span></span>
<span>(</span><span>*args, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>A ProtocolMessage</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li>google._upb._message.Message</li>
<li>google.protobuf.message.Message</li>
</ul>
<h3>Class variables</h3>
<dl>
<dt id="connpy.grpc.connpy_pb2.AskRequest.DESCRIPTOR"><code class="name">var <span class="ident">DESCRIPTOR</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
</dd>
<dt id="connpy.grpc.connpy_pb2.BoolResponse"><code class="flex name class">
<span>class <span class="ident">BoolResponse</span></span>
<span>(</span><span>*args, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>A ProtocolMessage</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li>google._upb._message.Message</li>
<li>google.protobuf.message.Message</li>
</ul>
<h3>Class variables</h3>
<dl>
<dt id="connpy.grpc.connpy_pb2.BoolResponse.DESCRIPTOR"><code class="name">var <span class="ident">DESCRIPTOR</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
</dd>
<dt id="connpy.grpc.connpy_pb2.BulkRequest"><code class="flex name class">
<span>class <span class="ident">BulkRequest</span></span>
<span>(</span><span>*args, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>A ProtocolMessage</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li>google._upb._message.Message</li>
<li>google.protobuf.message.Message</li>
</ul>
<h3>Class variables</h3>
<dl>
<dt id="connpy.grpc.connpy_pb2.BulkRequest.DESCRIPTOR"><code class="name">var <span class="ident">DESCRIPTOR</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
</dd>
<dt id="connpy.grpc.connpy_pb2.DeleteRequest"><code class="flex name class">
<span>class <span class="ident">DeleteRequest</span></span>
<span>(</span><span>*args, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>A ProtocolMessage</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li>google._upb._message.Message</li>
<li>google.protobuf.message.Message</li>
</ul>
<h3>Class variables</h3>
<dl>
<dt id="connpy.grpc.connpy_pb2.DeleteRequest.DESCRIPTOR"><code class="name">var <span class="ident">DESCRIPTOR</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
</dd>
<dt id="connpy.grpc.connpy_pb2.ExportRequest"><code class="flex name class">
<span>class <span class="ident">ExportRequest</span></span>
<span>(</span><span>*args, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>A ProtocolMessage</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li>google._upb._message.Message</li>
<li>google.protobuf.message.Message</li>
</ul>
<h3>Class variables</h3>
<dl>
<dt id="connpy.grpc.connpy_pb2.ExportRequest.DESCRIPTOR"><code class="name">var <span class="ident">DESCRIPTOR</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
</dd>
<dt id="connpy.grpc.connpy_pb2.FilterRequest"><code class="flex name class">
<span>class <span class="ident">FilterRequest</span></span>
<span>(</span><span>*args, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>A ProtocolMessage</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li>google._upb._message.Message</li>
<li>google.protobuf.message.Message</li>
</ul>
<h3>Class variables</h3>
<dl>
<dt id="connpy.grpc.connpy_pb2.FilterRequest.DESCRIPTOR"><code class="name">var <span class="ident">DESCRIPTOR</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
</dd>
<dt id="connpy.grpc.connpy_pb2.FullReplaceRequest"><code class="flex name class">
<span>class <span class="ident">FullReplaceRequest</span></span>
<span>(</span><span>*args, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>A ProtocolMessage</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li>google._upb._message.Message</li>
<li>google.protobuf.message.Message</li>
</ul>
<h3>Class variables</h3>
<dl>
<dt id="connpy.grpc.connpy_pb2.FullReplaceRequest.DESCRIPTOR"><code class="name">var <span class="ident">DESCRIPTOR</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
</dd>
<dt id="connpy.grpc.connpy_pb2.IdRequest"><code class="flex name class">
<span>class <span class="ident">IdRequest</span></span>
<span>(</span><span>*args, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>A ProtocolMessage</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li>google._upb._message.Message</li>
<li>google.protobuf.message.Message</li>
</ul>
<h3>Class variables</h3>
<dl>
<dt id="connpy.grpc.connpy_pb2.IdRequest.DESCRIPTOR"><code class="name">var <span class="ident">DESCRIPTOR</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
</dd>
<dt id="connpy.grpc.connpy_pb2.IntRequest"><code class="flex name class">
<span>class <span class="ident">IntRequest</span></span>
<span>(</span><span>*args, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>A ProtocolMessage</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li>google._upb._message.Message</li>
<li>google.protobuf.message.Message</li>
</ul>
<h3>Class variables</h3>
<dl>
<dt id="connpy.grpc.connpy_pb2.IntRequest.DESCRIPTOR"><code class="name">var <span class="ident">DESCRIPTOR</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
</dd>
<dt id="connpy.grpc.connpy_pb2.InteractRequest"><code class="flex name class">
<span>class <span class="ident">InteractRequest</span></span>
<span>(</span><span>*args, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>A ProtocolMessage</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li>google._upb._message.Message</li>
<li>google.protobuf.message.Message</li>
</ul>
<h3>Class variables</h3>
<dl>
<dt id="connpy.grpc.connpy_pb2.InteractRequest.DESCRIPTOR"><code class="name">var <span class="ident">DESCRIPTOR</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
</dd>
<dt id="connpy.grpc.connpy_pb2.InteractResponse"><code class="flex name class">
<span>class <span class="ident">InteractResponse</span></span>
<span>(</span><span>*args, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>A ProtocolMessage</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li>google._upb._message.Message</li>
<li>google.protobuf.message.Message</li>
</ul>
<h3>Class variables</h3>
<dl>
<dt id="connpy.grpc.connpy_pb2.InteractResponse.DESCRIPTOR"><code class="name">var <span class="ident">DESCRIPTOR</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
</dd>
<dt id="connpy.grpc.connpy_pb2.ListRequest"><code class="flex name class">
<span>class <span class="ident">ListRequest</span></span>
<span>(</span><span>*args, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>A ProtocolMessage</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li>google._upb._message.Message</li>
<li>google.protobuf.message.Message</li>
</ul>
<h3>Class variables</h3>
<dl>
<dt id="connpy.grpc.connpy_pb2.ListRequest.DESCRIPTOR"><code class="name">var <span class="ident">DESCRIPTOR</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
</dd>
<dt id="connpy.grpc.connpy_pb2.MessageValue"><code class="flex name class">
<span>class <span class="ident">MessageValue</span></span>
<span>(</span><span>*args, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>A ProtocolMessage</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li>google._upb._message.Message</li>
<li>google.protobuf.message.Message</li>
</ul>
<h3>Class variables</h3>
<dl>
<dt id="connpy.grpc.connpy_pb2.MessageValue.DESCRIPTOR"><code class="name">var <span class="ident">DESCRIPTOR</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
</dd>
<dt id="connpy.grpc.connpy_pb2.MoveRequest"><code class="flex name class">
<span>class <span class="ident">MoveRequest</span></span>
<span>(</span><span>*args, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>A ProtocolMessage</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li>google._upb._message.Message</li>
<li>google.protobuf.message.Message</li>
</ul>
<h3>Class variables</h3>
<dl>
<dt id="connpy.grpc.connpy_pb2.MoveRequest.DESCRIPTOR"><code class="name">var <span class="ident">DESCRIPTOR</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
</dd>
<dt id="connpy.grpc.connpy_pb2.NodeRequest"><code class="flex name class">
<span>class <span class="ident">NodeRequest</span></span>
<span>(</span><span>*args, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>A ProtocolMessage</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li>google._upb._message.Message</li>
<li>google.protobuf.message.Message</li>
</ul>
<h3>Class variables</h3>
<dl>
<dt id="connpy.grpc.connpy_pb2.NodeRequest.DESCRIPTOR"><code class="name">var <span class="ident">DESCRIPTOR</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
</dd>
<dt id="connpy.grpc.connpy_pb2.NodeRunResult"><code class="flex name class">
<span>class <span class="ident">NodeRunResult</span></span>
<span>(</span><span>*args, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>A ProtocolMessage</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li>google._upb._message.Message</li>
<li>google.protobuf.message.Message</li>
</ul>
<h3>Class variables</h3>
<dl>
<dt id="connpy.grpc.connpy_pb2.NodeRunResult.DESCRIPTOR"><code class="name">var <span class="ident">DESCRIPTOR</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
</dd>
<dt id="connpy.grpc.connpy_pb2.PluginRequest"><code class="flex name class">
<span>class <span class="ident">PluginRequest</span></span>
<span>(</span><span>*args, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>A ProtocolMessage</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li>google._upb._message.Message</li>
<li>google.protobuf.message.Message</li>
</ul>
<h3>Class variables</h3>
<dl>
<dt id="connpy.grpc.connpy_pb2.PluginRequest.DESCRIPTOR"><code class="name">var <span class="ident">DESCRIPTOR</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
</dd>
<dt id="connpy.grpc.connpy_pb2.ProfileRequest"><code class="flex name class">
<span>class <span class="ident">ProfileRequest</span></span>
<span>(</span><span>*args, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>A ProtocolMessage</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li>google._upb._message.Message</li>
<li>google.protobuf.message.Message</li>
</ul>
<h3>Class variables</h3>
<dl>
<dt id="connpy.grpc.connpy_pb2.ProfileRequest.DESCRIPTOR"><code class="name">var <span class="ident">DESCRIPTOR</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
</dd>
<dt id="connpy.grpc.connpy_pb2.ProviderRequest"><code class="flex name class">
<span>class <span class="ident">ProviderRequest</span></span>
<span>(</span><span>*args, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>A ProtocolMessage</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li>google._upb._message.Message</li>
<li>google.protobuf.message.Message</li>
</ul>
<h3>Class variables</h3>
<dl>
<dt id="connpy.grpc.connpy_pb2.ProviderRequest.DESCRIPTOR"><code class="name">var <span class="ident">DESCRIPTOR</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
</dd>
<dt id="connpy.grpc.connpy_pb2.RunRequest"><code class="flex name class">
<span>class <span class="ident">RunRequest</span></span>
<span>(</span><span>*args, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>A ProtocolMessage</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li>google._upb._message.Message</li>
<li>google.protobuf.message.Message</li>
</ul>
<h3>Class variables</h3>
<dl>
<dt id="connpy.grpc.connpy_pb2.RunRequest.DESCRIPTOR"><code class="name">var <span class="ident">DESCRIPTOR</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
</dd>
<dt id="connpy.grpc.connpy_pb2.ScriptRequest"><code class="flex name class">
<span>class <span class="ident">ScriptRequest</span></span>
<span>(</span><span>*args, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>A ProtocolMessage</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li>google._upb._message.Message</li>
<li>google.protobuf.message.Message</li>
</ul>
<h3>Class variables</h3>
<dl>
<dt id="connpy.grpc.connpy_pb2.ScriptRequest.DESCRIPTOR"><code class="name">var <span class="ident">DESCRIPTOR</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
</dd>
<dt id="connpy.grpc.connpy_pb2.StringRequest"><code class="flex name class">
<span>class <span class="ident">StringRequest</span></span>
<span>(</span><span>*args, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>A ProtocolMessage</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li>google._upb._message.Message</li>
<li>google.protobuf.message.Message</li>
</ul>
<h3>Class variables</h3>
<dl>
<dt id="connpy.grpc.connpy_pb2.StringRequest.DESCRIPTOR"><code class="name">var <span class="ident">DESCRIPTOR</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
</dd>
<dt id="connpy.grpc.connpy_pb2.StringResponse"><code class="flex name class">
<span>class <span class="ident">StringResponse</span></span>
<span>(</span><span>*args, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>A ProtocolMessage</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li>google._upb._message.Message</li>
<li>google.protobuf.message.Message</li>
</ul>
<h3>Class variables</h3>
<dl>
<dt id="connpy.grpc.connpy_pb2.StringResponse.DESCRIPTOR"><code class="name">var <span class="ident">DESCRIPTOR</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
</dd>
<dt id="connpy.grpc.connpy_pb2.StructRequest"><code class="flex name class">
<span>class <span class="ident">StructRequest</span></span>
<span>(</span><span>*args, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>A ProtocolMessage</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li>google._upb._message.Message</li>
<li>google.protobuf.message.Message</li>
</ul>
<h3>Class variables</h3>
<dl>
<dt id="connpy.grpc.connpy_pb2.StructRequest.DESCRIPTOR"><code class="name">var <span class="ident">DESCRIPTOR</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
</dd>
<dt id="connpy.grpc.connpy_pb2.StructResponse"><code class="flex name class">
<span>class <span class="ident">StructResponse</span></span>
<span>(</span><span>*args, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>A ProtocolMessage</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li>google._upb._message.Message</li>
<li>google.protobuf.message.Message</li>
</ul>
<h3>Class variables</h3>
<dl>
<dt id="connpy.grpc.connpy_pb2.StructResponse.DESCRIPTOR"><code class="name">var <span class="ident">DESCRIPTOR</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
</dd>
<dt id="connpy.grpc.connpy_pb2.TestRequest"><code class="flex name class">
<span>class <span class="ident">TestRequest</span></span>
<span>(</span><span>*args, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>A ProtocolMessage</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li>google._upb._message.Message</li>
<li>google.protobuf.message.Message</li>
</ul>
<h3>Class variables</h3>
<dl>
<dt id="connpy.grpc.connpy_pb2.TestRequest.DESCRIPTOR"><code class="name">var <span class="ident">DESCRIPTOR</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
</dd>
<dt id="connpy.grpc.connpy_pb2.UpdateRequest"><code class="flex name class">
<span>class <span class="ident">UpdateRequest</span></span>
<span>(</span><span>*args, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>A ProtocolMessage</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li>google._upb._message.Message</li>
<li>google.protobuf.message.Message</li>
</ul>
<h3>Class variables</h3>
<dl>
<dt id="connpy.grpc.connpy_pb2.UpdateRequest.DESCRIPTOR"><code class="name">var <span class="ident">DESCRIPTOR</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
</dd>
<dt id="connpy.grpc.connpy_pb2.ValueResponse"><code class="flex name class">
<span>class <span class="ident">ValueResponse</span></span>
<span>(</span><span>*args, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>A ProtocolMessage</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li>google._upb._message.Message</li>
<li>google.protobuf.message.Message</li>
</ul>
<h3>Class variables</h3>
<dl>
<dt id="connpy.grpc.connpy_pb2.ValueResponse.DESCRIPTOR"><code class="name">var <span class="ident">DESCRIPTOR</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
</dd>
</dl>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="connpy.grpc" href="index.html">connpy.grpc</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="connpy.grpc.connpy_pb2.AIResponse" href="#connpy.grpc.connpy_pb2.AIResponse">AIResponse</a></code></h4>
<ul class="">
<li><code><a title="connpy.grpc.connpy_pb2.AIResponse.DESCRIPTOR" href="#connpy.grpc.connpy_pb2.AIResponse.DESCRIPTOR">DESCRIPTOR</a></code></li>
</ul>
</li>
<li>
<h4><code><a title="connpy.grpc.connpy_pb2.AskRequest" href="#connpy.grpc.connpy_pb2.AskRequest">AskRequest</a></code></h4>
<ul class="">
<li><code><a title="connpy.grpc.connpy_pb2.AskRequest.DESCRIPTOR" href="#connpy.grpc.connpy_pb2.AskRequest.DESCRIPTOR">DESCRIPTOR</a></code></li>
</ul>
</li>
<li>
<h4><code><a title="connpy.grpc.connpy_pb2.BoolResponse" href="#connpy.grpc.connpy_pb2.BoolResponse">BoolResponse</a></code></h4>
<ul class="">
<li><code><a title="connpy.grpc.connpy_pb2.BoolResponse.DESCRIPTOR" href="#connpy.grpc.connpy_pb2.BoolResponse.DESCRIPTOR">DESCRIPTOR</a></code></li>
</ul>
</li>
<li>
<h4><code><a title="connpy.grpc.connpy_pb2.BulkRequest" href="#connpy.grpc.connpy_pb2.BulkRequest">BulkRequest</a></code></h4>
<ul class="">
<li><code><a title="connpy.grpc.connpy_pb2.BulkRequest.DESCRIPTOR" href="#connpy.grpc.connpy_pb2.BulkRequest.DESCRIPTOR">DESCRIPTOR</a></code></li>
</ul>
</li>
<li>
<h4><code><a title="connpy.grpc.connpy_pb2.DeleteRequest" href="#connpy.grpc.connpy_pb2.DeleteRequest">DeleteRequest</a></code></h4>
<ul class="">
<li><code><a title="connpy.grpc.connpy_pb2.DeleteRequest.DESCRIPTOR" href="#connpy.grpc.connpy_pb2.DeleteRequest.DESCRIPTOR">DESCRIPTOR</a></code></li>
</ul>
</li>
<li>
<h4><code><a title="connpy.grpc.connpy_pb2.ExportRequest" href="#connpy.grpc.connpy_pb2.ExportRequest">ExportRequest</a></code></h4>
<ul class="">
<li><code><a title="connpy.grpc.connpy_pb2.ExportRequest.DESCRIPTOR" href="#connpy.grpc.connpy_pb2.ExportRequest.DESCRIPTOR">DESCRIPTOR</a></code></li>
</ul>
</li>
<li>
<h4><code><a title="connpy.grpc.connpy_pb2.FilterRequest" href="#connpy.grpc.connpy_pb2.FilterRequest">FilterRequest</a></code></h4>
<ul class="">
<li><code><a title="connpy.grpc.connpy_pb2.FilterRequest.DESCRIPTOR" href="#connpy.grpc.connpy_pb2.FilterRequest.DESCRIPTOR">DESCRIPTOR</a></code></li>
</ul>
</li>
<li>
<h4><code><a title="connpy.grpc.connpy_pb2.FullReplaceRequest" href="#connpy.grpc.connpy_pb2.FullReplaceRequest">FullReplaceRequest</a></code></h4>
<ul class="">
<li><code><a title="connpy.grpc.connpy_pb2.FullReplaceRequest.DESCRIPTOR" href="#connpy.grpc.connpy_pb2.FullReplaceRequest.DESCRIPTOR">DESCRIPTOR</a></code></li>
</ul>
</li>
<li>
<h4><code><a title="connpy.grpc.connpy_pb2.IdRequest" href="#connpy.grpc.connpy_pb2.IdRequest">IdRequest</a></code></h4>
<ul class="">
<li><code><a title="connpy.grpc.connpy_pb2.IdRequest.DESCRIPTOR" href="#connpy.grpc.connpy_pb2.IdRequest.DESCRIPTOR">DESCRIPTOR</a></code></li>
</ul>
</li>
<li>
<h4><code><a title="connpy.grpc.connpy_pb2.IntRequest" href="#connpy.grpc.connpy_pb2.IntRequest">IntRequest</a></code></h4>
<ul class="">
<li><code><a title="connpy.grpc.connpy_pb2.IntRequest.DESCRIPTOR" href="#connpy.grpc.connpy_pb2.IntRequest.DESCRIPTOR">DESCRIPTOR</a></code></li>
</ul>
</li>
<li>
<h4><code><a title="connpy.grpc.connpy_pb2.InteractRequest" href="#connpy.grpc.connpy_pb2.InteractRequest">InteractRequest</a></code></h4>
<ul class="">
<li><code><a title="connpy.grpc.connpy_pb2.InteractRequest.DESCRIPTOR" href="#connpy.grpc.connpy_pb2.InteractRequest.DESCRIPTOR">DESCRIPTOR</a></code></li>
</ul>
</li>
<li>
<h4><code><a title="connpy.grpc.connpy_pb2.InteractResponse" href="#connpy.grpc.connpy_pb2.InteractResponse">InteractResponse</a></code></h4>
<ul class="">
<li><code><a title="connpy.grpc.connpy_pb2.InteractResponse.DESCRIPTOR" href="#connpy.grpc.connpy_pb2.InteractResponse.DESCRIPTOR">DESCRIPTOR</a></code></li>
</ul>
</li>
<li>
<h4><code><a title="connpy.grpc.connpy_pb2.ListRequest" href="#connpy.grpc.connpy_pb2.ListRequest">ListRequest</a></code></h4>
<ul class="">
<li><code><a title="connpy.grpc.connpy_pb2.ListRequest.DESCRIPTOR" href="#connpy.grpc.connpy_pb2.ListRequest.DESCRIPTOR">DESCRIPTOR</a></code></li>
</ul>
</li>
<li>
<h4><code><a title="connpy.grpc.connpy_pb2.MessageValue" href="#connpy.grpc.connpy_pb2.MessageValue">MessageValue</a></code></h4>
<ul class="">
<li><code><a title="connpy.grpc.connpy_pb2.MessageValue.DESCRIPTOR" href="#connpy.grpc.connpy_pb2.MessageValue.DESCRIPTOR">DESCRIPTOR</a></code></li>
</ul>
</li>
<li>
<h4><code><a title="connpy.grpc.connpy_pb2.MoveRequest" href="#connpy.grpc.connpy_pb2.MoveRequest">MoveRequest</a></code></h4>
<ul class="">
<li><code><a title="connpy.grpc.connpy_pb2.MoveRequest.DESCRIPTOR" href="#connpy.grpc.connpy_pb2.MoveRequest.DESCRIPTOR">DESCRIPTOR</a></code></li>
</ul>
</li>
<li>
<h4><code><a title="connpy.grpc.connpy_pb2.NodeRequest" href="#connpy.grpc.connpy_pb2.NodeRequest">NodeRequest</a></code></h4>
<ul class="">
<li><code><a title="connpy.grpc.connpy_pb2.NodeRequest.DESCRIPTOR" href="#connpy.grpc.connpy_pb2.NodeRequest.DESCRIPTOR">DESCRIPTOR</a></code></li>
</ul>
</li>
<li>
<h4><code><a title="connpy.grpc.connpy_pb2.NodeRunResult" href="#connpy.grpc.connpy_pb2.NodeRunResult">NodeRunResult</a></code></h4>
<ul class="">
<li><code><a title="connpy.grpc.connpy_pb2.NodeRunResult.DESCRIPTOR" href="#connpy.grpc.connpy_pb2.NodeRunResult.DESCRIPTOR">DESCRIPTOR</a></code></li>
</ul>
</li>
<li>
<h4><code><a title="connpy.grpc.connpy_pb2.PluginRequest" href="#connpy.grpc.connpy_pb2.PluginRequest">PluginRequest</a></code></h4>
<ul class="">
<li><code><a title="connpy.grpc.connpy_pb2.PluginRequest.DESCRIPTOR" href="#connpy.grpc.connpy_pb2.PluginRequest.DESCRIPTOR">DESCRIPTOR</a></code></li>
</ul>
</li>
<li>
<h4><code><a title="connpy.grpc.connpy_pb2.ProfileRequest" href="#connpy.grpc.connpy_pb2.ProfileRequest">ProfileRequest</a></code></h4>
<ul class="">
<li><code><a title="connpy.grpc.connpy_pb2.ProfileRequest.DESCRIPTOR" href="#connpy.grpc.connpy_pb2.ProfileRequest.DESCRIPTOR">DESCRIPTOR</a></code></li>
</ul>
</li>
<li>
<h4><code><a title="connpy.grpc.connpy_pb2.ProviderRequest" href="#connpy.grpc.connpy_pb2.ProviderRequest">ProviderRequest</a></code></h4>
<ul class="">
<li><code><a title="connpy.grpc.connpy_pb2.ProviderRequest.DESCRIPTOR" href="#connpy.grpc.connpy_pb2.ProviderRequest.DESCRIPTOR">DESCRIPTOR</a></code></li>
</ul>
</li>
<li>
<h4><code><a title="connpy.grpc.connpy_pb2.RunRequest" href="#connpy.grpc.connpy_pb2.RunRequest">RunRequest</a></code></h4>
<ul class="">
<li><code><a title="connpy.grpc.connpy_pb2.RunRequest.DESCRIPTOR" href="#connpy.grpc.connpy_pb2.RunRequest.DESCRIPTOR">DESCRIPTOR</a></code></li>
</ul>
</li>
<li>
<h4><code><a title="connpy.grpc.connpy_pb2.ScriptRequest" href="#connpy.grpc.connpy_pb2.ScriptRequest">ScriptRequest</a></code></h4>
<ul class="">
<li><code><a title="connpy.grpc.connpy_pb2.ScriptRequest.DESCRIPTOR" href="#connpy.grpc.connpy_pb2.ScriptRequest.DESCRIPTOR">DESCRIPTOR</a></code></li>
</ul>
</li>
<li>
<h4><code><a title="connpy.grpc.connpy_pb2.StringRequest" href="#connpy.grpc.connpy_pb2.StringRequest">StringRequest</a></code></h4>
<ul class="">
<li><code><a title="connpy.grpc.connpy_pb2.StringRequest.DESCRIPTOR" href="#connpy.grpc.connpy_pb2.StringRequest.DESCRIPTOR">DESCRIPTOR</a></code></li>
</ul>
</li>
<li>
<h4><code><a title="connpy.grpc.connpy_pb2.StringResponse" href="#connpy.grpc.connpy_pb2.StringResponse">StringResponse</a></code></h4>
<ul class="">
<li><code><a title="connpy.grpc.connpy_pb2.StringResponse.DESCRIPTOR" href="#connpy.grpc.connpy_pb2.StringResponse.DESCRIPTOR">DESCRIPTOR</a></code></li>
</ul>
</li>
<li>
<h4><code><a title="connpy.grpc.connpy_pb2.StructRequest" href="#connpy.grpc.connpy_pb2.StructRequest">StructRequest</a></code></h4>
<ul class="">
<li><code><a title="connpy.grpc.connpy_pb2.StructRequest.DESCRIPTOR" href="#connpy.grpc.connpy_pb2.StructRequest.DESCRIPTOR">DESCRIPTOR</a></code></li>
</ul>
</li>
<li>
<h4><code><a title="connpy.grpc.connpy_pb2.StructResponse" href="#connpy.grpc.connpy_pb2.StructResponse">StructResponse</a></code></h4>
<ul class="">
<li><code><a title="connpy.grpc.connpy_pb2.StructResponse.DESCRIPTOR" href="#connpy.grpc.connpy_pb2.StructResponse.DESCRIPTOR">DESCRIPTOR</a></code></li>
</ul>
</li>
<li>
<h4><code><a title="connpy.grpc.connpy_pb2.TestRequest" href="#connpy.grpc.connpy_pb2.TestRequest">TestRequest</a></code></h4>
<ul class="">
<li><code><a title="connpy.grpc.connpy_pb2.TestRequest.DESCRIPTOR" href="#connpy.grpc.connpy_pb2.TestRequest.DESCRIPTOR">DESCRIPTOR</a></code></li>
</ul>
</li>
<li>
<h4><code><a title="connpy.grpc.connpy_pb2.UpdateRequest" href="#connpy.grpc.connpy_pb2.UpdateRequest">UpdateRequest</a></code></h4>
<ul class="">
<li><code><a title="connpy.grpc.connpy_pb2.UpdateRequest.DESCRIPTOR" href="#connpy.grpc.connpy_pb2.UpdateRequest.DESCRIPTOR">DESCRIPTOR</a></code></li>
</ul>
</li>
<li>
<h4><code><a title="connpy.grpc.connpy_pb2.ValueResponse" href="#connpy.grpc.connpy_pb2.ValueResponse">ValueResponse</a></code></h4>
<ul class="">
<li><code><a title="connpy.grpc.connpy_pb2.ValueResponse.DESCRIPTOR" href="#connpy.grpc.connpy_pb2.ValueResponse.DESCRIPTOR">DESCRIPTOR</a></code></li>
</ul>
</li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.5</a>.</p>
</footer>
</body>
</html>
File diff suppressed because it is too large Load Diff
+108
View File
@@ -0,0 +1,108 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.5">
<title>connpy.grpc API documentation</title>
<meta name="description" content="">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source > summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible;min-width:max-content}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin:1em 0}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
/* Collapse source docstrings */
setTimeout(() => {
[...document.querySelectorAll('.hljs.language-python > .hljs-string')]
.filter(el => el.innerHTML.length > 200 && ['"""', "'''"].includes(el.innerHTML.substring(0, 3)))
.forEach(el => {
let d = document.createElement('details');
d.classList.add('hljs-string');
d.innerHTML = '<summary>"""</summary>' + el.innerHTML.substring(3);
el.replaceWith(d);
});
}, 100);
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Namespace <code>connpy.grpc</code></h1>
</header>
<section id="section-intro">
</section>
<section>
<h2 class="section-title" id="header-submodules">Sub-modules</h2>
<dl>
<dt><code class="name"><a title="connpy.grpc.connpy_pb2" href="connpy_pb2.html">connpy.grpc.connpy_pb2</a></code></dt>
<dd>
<div class="desc"><p>Generated protocol buffer code.</p></div>
</dd>
<dt><code class="name"><a title="connpy.grpc.connpy_pb2_grpc" href="connpy_pb2_grpc.html">connpy.grpc.connpy_pb2_grpc</a></code></dt>
<dd>
<div class="desc"><p>Client and server classes corresponding to protobuf-defined services.</p></div>
</dd>
<dt><code class="name"><a title="connpy.grpc.remote_plugin_pb2" href="remote_plugin_pb2.html">connpy.grpc.remote_plugin_pb2</a></code></dt>
<dd>
<div class="desc"><p>Generated protocol buffer code.</p></div>
</dd>
<dt><code class="name"><a title="connpy.grpc.remote_plugin_pb2_grpc" href="remote_plugin_pb2_grpc.html">connpy.grpc.remote_plugin_pb2_grpc</a></code></dt>
<dd>
<div class="desc"><p>Client and server classes corresponding to protobuf-defined services.</p></div>
</dd>
<dt><code class="name"><a title="connpy.grpc.server" href="server.html">connpy.grpc.server</a></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt><code class="name"><a title="connpy.grpc.stubs" href="stubs.html">connpy.grpc.stubs</a></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt><code class="name"><a title="connpy.grpc.utils" href="utils.html">connpy.grpc.utils</a></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
</section>
<section>
</section>
<section>
</section>
<section>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="connpy" href="../index.html">connpy</a></code></li>
</ul>
</li>
<li><h3><a href="#header-submodules">Sub-modules</a></h3>
<ul>
<li><code><a title="connpy.grpc.connpy_pb2" href="connpy_pb2.html">connpy.grpc.connpy_pb2</a></code></li>
<li><code><a title="connpy.grpc.connpy_pb2_grpc" href="connpy_pb2_grpc.html">connpy.grpc.connpy_pb2_grpc</a></code></li>
<li><code><a title="connpy.grpc.remote_plugin_pb2" href="remote_plugin_pb2.html">connpy.grpc.remote_plugin_pb2</a></code></li>
<li><code><a title="connpy.grpc.remote_plugin_pb2_grpc" href="remote_plugin_pb2_grpc.html">connpy.grpc.remote_plugin_pb2_grpc</a></code></li>
<li><code><a title="connpy.grpc.server" href="server.html">connpy.grpc.server</a></code></li>
<li><code><a title="connpy.grpc.stubs" href="stubs.html">connpy.grpc.stubs</a></code></li>
<li><code><a title="connpy.grpc.utils" href="utils.html">connpy.grpc.utils</a></code></li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.5</a>.</p>
</footer>
</body>
</html>
+174
View File
@@ -0,0 +1,174 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.5">
<title>connpy.grpc.remote_plugin_pb2 API documentation</title>
<meta name="description" content="Generated protocol buffer code.">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source > summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible;min-width:max-content}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin:1em 0}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
/* Collapse source docstrings */
setTimeout(() => {
[...document.querySelectorAll('.hljs.language-python > .hljs-string')]
.filter(el => el.innerHTML.length > 200 && ['"""', "'''"].includes(el.innerHTML.substring(0, 3)))
.forEach(el => {
let d = document.createElement('details');
d.classList.add('hljs-string');
d.innerHTML = '<summary>"""</summary>' + el.innerHTML.substring(3);
el.replaceWith(d);
});
}, 100);
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>connpy.grpc.remote_plugin_pb2</code></h1>
</header>
<section id="section-intro">
<p>Generated protocol buffer code.</p>
</section>
<section>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="connpy.grpc.remote_plugin_pb2.IdRequest"><code class="flex name class">
<span>class <span class="ident">IdRequest</span></span>
<span>(</span><span>*args, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>A ProtocolMessage</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li>google._upb._message.Message</li>
<li>google.protobuf.message.Message</li>
</ul>
<h3>Class variables</h3>
<dl>
<dt id="connpy.grpc.remote_plugin_pb2.IdRequest.DESCRIPTOR"><code class="name">var <span class="ident">DESCRIPTOR</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
</dd>
<dt id="connpy.grpc.remote_plugin_pb2.OutputChunk"><code class="flex name class">
<span>class <span class="ident">OutputChunk</span></span>
<span>(</span><span>*args, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>A ProtocolMessage</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li>google._upb._message.Message</li>
<li>google.protobuf.message.Message</li>
</ul>
<h3>Class variables</h3>
<dl>
<dt id="connpy.grpc.remote_plugin_pb2.OutputChunk.DESCRIPTOR"><code class="name">var <span class="ident">DESCRIPTOR</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
</dd>
<dt id="connpy.grpc.remote_plugin_pb2.PluginInvokeRequest"><code class="flex name class">
<span>class <span class="ident">PluginInvokeRequest</span></span>
<span>(</span><span>*args, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>A ProtocolMessage</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li>google._upb._message.Message</li>
<li>google.protobuf.message.Message</li>
</ul>
<h3>Class variables</h3>
<dl>
<dt id="connpy.grpc.remote_plugin_pb2.PluginInvokeRequest.DESCRIPTOR"><code class="name">var <span class="ident">DESCRIPTOR</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
</dd>
<dt id="connpy.grpc.remote_plugin_pb2.StringResponse"><code class="flex name class">
<span>class <span class="ident">StringResponse</span></span>
<span>(</span><span>*args, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>A ProtocolMessage</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li>google._upb._message.Message</li>
<li>google.protobuf.message.Message</li>
</ul>
<h3>Class variables</h3>
<dl>
<dt id="connpy.grpc.remote_plugin_pb2.StringResponse.DESCRIPTOR"><code class="name">var <span class="ident">DESCRIPTOR</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
</dd>
</dl>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="connpy.grpc" href="index.html">connpy.grpc</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="connpy.grpc.remote_plugin_pb2.IdRequest" href="#connpy.grpc.remote_plugin_pb2.IdRequest">IdRequest</a></code></h4>
<ul class="">
<li><code><a title="connpy.grpc.remote_plugin_pb2.IdRequest.DESCRIPTOR" href="#connpy.grpc.remote_plugin_pb2.IdRequest.DESCRIPTOR">DESCRIPTOR</a></code></li>
</ul>
</li>
<li>
<h4><code><a title="connpy.grpc.remote_plugin_pb2.OutputChunk" href="#connpy.grpc.remote_plugin_pb2.OutputChunk">OutputChunk</a></code></h4>
<ul class="">
<li><code><a title="connpy.grpc.remote_plugin_pb2.OutputChunk.DESCRIPTOR" href="#connpy.grpc.remote_plugin_pb2.OutputChunk.DESCRIPTOR">DESCRIPTOR</a></code></li>
</ul>
</li>
<li>
<h4><code><a title="connpy.grpc.remote_plugin_pb2.PluginInvokeRequest" href="#connpy.grpc.remote_plugin_pb2.PluginInvokeRequest">PluginInvokeRequest</a></code></h4>
<ul class="">
<li><code><a title="connpy.grpc.remote_plugin_pb2.PluginInvokeRequest.DESCRIPTOR" href="#connpy.grpc.remote_plugin_pb2.PluginInvokeRequest.DESCRIPTOR">DESCRIPTOR</a></code></li>
</ul>
</li>
<li>
<h4><code><a title="connpy.grpc.remote_plugin_pb2.StringResponse" href="#connpy.grpc.remote_plugin_pb2.StringResponse">StringResponse</a></code></h4>
<ul class="">
<li><code><a title="connpy.grpc.remote_plugin_pb2.StringResponse.DESCRIPTOR" href="#connpy.grpc.remote_plugin_pb2.StringResponse.DESCRIPTOR">DESCRIPTOR</a></code></li>
</ul>
</li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.5</a>.</p>
</footer>
</body>
</html>
@@ -0,0 +1,372 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.5">
<title>connpy.grpc.remote_plugin_pb2_grpc API documentation</title>
<meta name="description" content="Client and server classes corresponding to protobuf-defined services.">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source > summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible;min-width:max-content}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin:1em 0}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
/* Collapse source docstrings */
setTimeout(() => {
[...document.querySelectorAll('.hljs.language-python > .hljs-string')]
.filter(el => el.innerHTML.length > 200 && ['"""', "'''"].includes(el.innerHTML.substring(0, 3)))
.forEach(el => {
let d = document.createElement('details');
d.classList.add('hljs-string');
d.innerHTML = '<summary>"""</summary>' + el.innerHTML.substring(3);
el.replaceWith(d);
});
}, 100);
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>connpy.grpc.remote_plugin_pb2_grpc</code></h1>
</header>
<section id="section-intro">
<p>Client and server classes corresponding to protobuf-defined services.</p>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-functions">Functions</h2>
<dl>
<dt id="connpy.grpc.remote_plugin_pb2_grpc.add_RemotePluginServiceServicer_to_server"><code class="name flex">
<span>def <span class="ident">add_RemotePluginServiceServicer_to_server</span></span>(<span>servicer, server)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def add_RemotePluginServiceServicer_to_server(servicer, server):
rpc_method_handlers = {
&#39;get_plugin_source&#39;: grpc.unary_unary_rpc_method_handler(
servicer.get_plugin_source,
request_deserializer=remote__plugin__pb2.IdRequest.FromString,
response_serializer=remote__plugin__pb2.StringResponse.SerializeToString,
),
&#39;invoke_plugin&#39;: grpc.unary_stream_rpc_method_handler(
servicer.invoke_plugin,
request_deserializer=remote__plugin__pb2.PluginInvokeRequest.FromString,
response_serializer=remote__plugin__pb2.OutputChunk.SerializeToString,
),
}
generic_handler = grpc.method_handlers_generic_handler(
&#39;connpy_remote.RemotePluginService&#39;, rpc_method_handlers)
server.add_generic_rpc_handlers((generic_handler,))
server.add_registered_method_handlers(&#39;connpy_remote.RemotePluginService&#39;, rpc_method_handlers)</code></pre>
</details>
<div class="desc"></div>
</dd>
</dl>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="connpy.grpc.remote_plugin_pb2_grpc.RemotePluginService"><code class="flex name class">
<span>class <span class="ident">RemotePluginService</span></span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class RemotePluginService(object):
&#34;&#34;&#34;Missing associated documentation comment in .proto file.&#34;&#34;&#34;
@staticmethod
def get_plugin_source(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
&#39;/connpy_remote.RemotePluginService/get_plugin_source&#39;,
remote__plugin__pb2.IdRequest.SerializeToString,
remote__plugin__pb2.StringResponse.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def invoke_plugin(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_stream(
request,
target,
&#39;/connpy_remote.RemotePluginService/invoke_plugin&#39;,
remote__plugin__pb2.PluginInvokeRequest.SerializeToString,
remote__plugin__pb2.OutputChunk.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)</code></pre>
</details>
<div class="desc"><p>Missing associated documentation comment in .proto file.</p></div>
<h3>Static methods</h3>
<dl>
<dt id="connpy.grpc.remote_plugin_pb2_grpc.RemotePluginService.get_plugin_source"><code class="name flex">
<span>def <span class="ident">get_plugin_source</span></span>(<span>request,<br>target,<br>options=(),<br>channel_credentials=None,<br>call_credentials=None,<br>insecure=False,<br>compression=None,<br>wait_for_ready=None,<br>timeout=None,<br>metadata=None)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@staticmethod
def get_plugin_source(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
&#39;/connpy_remote.RemotePluginService/get_plugin_source&#39;,
remote__plugin__pb2.IdRequest.SerializeToString,
remote__plugin__pb2.StringResponse.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.grpc.remote_plugin_pb2_grpc.RemotePluginService.invoke_plugin"><code class="name flex">
<span>def <span class="ident">invoke_plugin</span></span>(<span>request,<br>target,<br>options=(),<br>channel_credentials=None,<br>call_credentials=None,<br>insecure=False,<br>compression=None,<br>wait_for_ready=None,<br>timeout=None,<br>metadata=None)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@staticmethod
def invoke_plugin(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_stream(
request,
target,
&#39;/connpy_remote.RemotePluginService/invoke_plugin&#39;,
remote__plugin__pb2.PluginInvokeRequest.SerializeToString,
remote__plugin__pb2.OutputChunk.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)</code></pre>
</details>
<div class="desc"></div>
</dd>
</dl>
</dd>
<dt id="connpy.grpc.remote_plugin_pb2_grpc.RemotePluginServiceServicer"><code class="flex name class">
<span>class <span class="ident">RemotePluginServiceServicer</span></span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class RemotePluginServiceServicer(object):
&#34;&#34;&#34;Missing associated documentation comment in .proto file.&#34;&#34;&#34;
def get_plugin_source(self, request, context):
&#34;&#34;&#34;Missing associated documentation comment in .proto file.&#34;&#34;&#34;
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details(&#39;Method not implemented!&#39;)
raise NotImplementedError(&#39;Method not implemented!&#39;)
def invoke_plugin(self, request, context):
&#34;&#34;&#34;Missing associated documentation comment in .proto file.&#34;&#34;&#34;
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details(&#39;Method not implemented!&#39;)
raise NotImplementedError(&#39;Method not implemented!&#39;)</code></pre>
</details>
<div class="desc"><p>Missing associated documentation comment in .proto file.</p></div>
<h3>Subclasses</h3>
<ul class="hlist">
<li><a title="connpy.grpc.server.PluginServicer" href="server.html#connpy.grpc.server.PluginServicer">PluginServicer</a></li>
</ul>
<h3>Methods</h3>
<dl>
<dt id="connpy.grpc.remote_plugin_pb2_grpc.RemotePluginServiceServicer.get_plugin_source"><code class="name flex">
<span>def <span class="ident">get_plugin_source</span></span>(<span>self, request, context)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def get_plugin_source(self, request, context):
&#34;&#34;&#34;Missing associated documentation comment in .proto file.&#34;&#34;&#34;
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details(&#39;Method not implemented!&#39;)
raise NotImplementedError(&#39;Method not implemented!&#39;)</code></pre>
</details>
<div class="desc"><p>Missing associated documentation comment in .proto file.</p></div>
</dd>
<dt id="connpy.grpc.remote_plugin_pb2_grpc.RemotePluginServiceServicer.invoke_plugin"><code class="name flex">
<span>def <span class="ident">invoke_plugin</span></span>(<span>self, request, context)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def invoke_plugin(self, request, context):
&#34;&#34;&#34;Missing associated documentation comment in .proto file.&#34;&#34;&#34;
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details(&#39;Method not implemented!&#39;)
raise NotImplementedError(&#39;Method not implemented!&#39;)</code></pre>
</details>
<div class="desc"><p>Missing associated documentation comment in .proto file.</p></div>
</dd>
</dl>
</dd>
<dt id="connpy.grpc.remote_plugin_pb2_grpc.RemotePluginServiceStub"><code class="flex name class">
<span>class <span class="ident">RemotePluginServiceStub</span></span>
<span>(</span><span>channel)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class RemotePluginServiceStub(object):
&#34;&#34;&#34;Missing associated documentation comment in .proto file.&#34;&#34;&#34;
def __init__(self, channel):
&#34;&#34;&#34;Constructor.
Args:
channel: A grpc.Channel.
&#34;&#34;&#34;
self.get_plugin_source = channel.unary_unary(
&#39;/connpy_remote.RemotePluginService/get_plugin_source&#39;,
request_serializer=remote__plugin__pb2.IdRequest.SerializeToString,
response_deserializer=remote__plugin__pb2.StringResponse.FromString,
_registered_method=True)
self.invoke_plugin = channel.unary_stream(
&#39;/connpy_remote.RemotePluginService/invoke_plugin&#39;,
request_serializer=remote__plugin__pb2.PluginInvokeRequest.SerializeToString,
response_deserializer=remote__plugin__pb2.OutputChunk.FromString,
_registered_method=True)</code></pre>
</details>
<div class="desc"><p>Missing associated documentation comment in .proto file.</p>
<p>Constructor.</p>
<h2 id="args">Args</h2>
<dl>
<dt><strong><code>channel</code></strong></dt>
<dd>A grpc.Channel.</dd>
</dl></div>
</dd>
</dl>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="connpy.grpc" href="index.html">connpy.grpc</a></code></li>
</ul>
</li>
<li><h3><a href="#header-functions">Functions</a></h3>
<ul class="">
<li><code><a title="connpy.grpc.remote_plugin_pb2_grpc.add_RemotePluginServiceServicer_to_server" href="#connpy.grpc.remote_plugin_pb2_grpc.add_RemotePluginServiceServicer_to_server">add_RemotePluginServiceServicer_to_server</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="connpy.grpc.remote_plugin_pb2_grpc.RemotePluginService" href="#connpy.grpc.remote_plugin_pb2_grpc.RemotePluginService">RemotePluginService</a></code></h4>
<ul class="">
<li><code><a title="connpy.grpc.remote_plugin_pb2_grpc.RemotePluginService.get_plugin_source" href="#connpy.grpc.remote_plugin_pb2_grpc.RemotePluginService.get_plugin_source">get_plugin_source</a></code></li>
<li><code><a title="connpy.grpc.remote_plugin_pb2_grpc.RemotePluginService.invoke_plugin" href="#connpy.grpc.remote_plugin_pb2_grpc.RemotePluginService.invoke_plugin">invoke_plugin</a></code></li>
</ul>
</li>
<li>
<h4><code><a title="connpy.grpc.remote_plugin_pb2_grpc.RemotePluginServiceServicer" href="#connpy.grpc.remote_plugin_pb2_grpc.RemotePluginServiceServicer">RemotePluginServiceServicer</a></code></h4>
<ul class="">
<li><code><a title="connpy.grpc.remote_plugin_pb2_grpc.RemotePluginServiceServicer.get_plugin_source" href="#connpy.grpc.remote_plugin_pb2_grpc.RemotePluginServiceServicer.get_plugin_source">get_plugin_source</a></code></li>
<li><code><a title="connpy.grpc.remote_plugin_pb2_grpc.RemotePluginServiceServicer.invoke_plugin" href="#connpy.grpc.remote_plugin_pb2_grpc.RemotePluginServiceServicer.invoke_plugin">invoke_plugin</a></code></li>
</ul>
</li>
<li>
<h4><code><a title="connpy.grpc.remote_plugin_pb2_grpc.RemotePluginServiceStub" href="#connpy.grpc.remote_plugin_pb2_grpc.RemotePluginServiceStub">RemotePluginServiceStub</a></code></h4>
</li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.5</a>.</p>
</footer>
</body>
</html>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+144
View File
@@ -0,0 +1,144 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.5">
<title>connpy.grpc.utils API documentation</title>
<meta name="description" content="">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source > summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible;min-width:max-content}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin:1em 0}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
/* Collapse source docstrings */
setTimeout(() => {
[...document.querySelectorAll('.hljs.language-python > .hljs-string')]
.filter(el => el.innerHTML.length > 200 && ['"""', "'''"].includes(el.innerHTML.substring(0, 3)))
.forEach(el => {
let d = document.createElement('details');
d.classList.add('hljs-string');
d.innerHTML = '<summary>"""</summary>' + el.innerHTML.substring(3);
el.replaceWith(d);
});
}, 100);
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>connpy.grpc.utils</code></h1>
</header>
<section id="section-intro">
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-functions">Functions</h2>
<dl>
<dt id="connpy.grpc.utils.from_struct"><code class="name flex">
<span>def <span class="ident">from_struct</span></span>(<span>struct)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def from_struct(struct):
if not struct:
return {}
return json_format.MessageToDict(struct, preserving_proto_field_name=True)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.grpc.utils.from_value"><code class="name flex">
<span>def <span class="ident">from_value</span></span>(<span>val)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def from_value(val):
if not val.HasField(&#34;kind&#34;):
return None
return json.loads(json_format.MessageToJson(val))</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.grpc.utils.to_struct"><code class="name flex">
<span>def <span class="ident">to_struct</span></span>(<span>obj)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def to_struct(obj):
if not obj:
return Struct()
s = Struct()
json_format.ParseDict(obj, s)
return s</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.grpc.utils.to_value"><code class="name flex">
<span>def <span class="ident">to_value</span></span>(<span>obj)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def to_value(obj):
if obj is None:
v = Value()
v.null_value = 0
return v
json_str = json.dumps(obj)
v = Value()
json_format.Parse(json_str, v)
return v</code></pre>
</details>
<div class="desc"></div>
</dd>
</dl>
</section>
<section>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="connpy.grpc" href="index.html">connpy.grpc</a></code></li>
</ul>
</li>
<li><h3><a href="#header-functions">Functions</a></h3>
<ul class="">
<li><code><a title="connpy.grpc.utils.from_struct" href="#connpy.grpc.utils.from_struct">from_struct</a></code></li>
<li><code><a title="connpy.grpc.utils.from_value" href="#connpy.grpc.utils.from_value">from_value</a></code></li>
<li><code><a title="connpy.grpc.utils.to_struct" href="#connpy.grpc.utils.to_struct">to_struct</a></code></li>
<li><code><a title="connpy.grpc.utils.to_value" href="#connpy.grpc.utils.to_value">to_value</a></code></li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.5</a>.</p>
</footer>
</body>
</html>
+648 -363
View File
File diff suppressed because it is too large Load Diff
+271
View File
@@ -0,0 +1,271 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.5">
<title>connpy.services.ai_service API documentation</title>
<meta name="description" content="">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source > summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible;min-width:max-content}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin:1em 0}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
/* Collapse source docstrings */
setTimeout(() => {
[...document.querySelectorAll('.hljs.language-python > .hljs-string')]
.filter(el => el.innerHTML.length > 200 && ['"""', "'''"].includes(el.innerHTML.substring(0, 3)))
.forEach(el => {
let d = document.createElement('details');
d.classList.add('hljs-string');
d.innerHTML = '<summary>"""</summary>' + el.innerHTML.substring(3);
el.replaceWith(d);
});
}, 100);
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>connpy.services.ai_service</code></h1>
</header>
<section id="section-intro">
</section>
<section>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="connpy.services.ai_service.AIService"><code class="flex name class">
<span>class <span class="ident">AIService</span></span>
<span>(</span><span>config=None)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class AIService(BaseService):
&#34;&#34;&#34;Business logic for interacting with AI agents and LLM configurations.&#34;&#34;&#34;
def ask(self, input_text, dryrun=False, chat_history=None, status=None, debug=False, session_id=None, console=None, chunk_callback=None, confirm_handler=None, trust=False, **overrides):
&#34;&#34;&#34;Send a prompt to the AI agent.&#34;&#34;&#34;
from connpy.ai import ai
agent = ai(self.config, console=console, confirm_handler=confirm_handler, trust=trust, **overrides)
return agent.ask(input_text, dryrun, chat_history, status=status, debug=debug, session_id=session_id, chunk_callback=chunk_callback)
def confirm(self, input_text, console=None):
&#34;&#34;&#34;Ask for a safe confirmation of an action.&#34;&#34;&#34;
from connpy.ai import ai
agent = ai(self.config, console=console)
return agent.confirm(input_text)
def list_sessions(self):
&#34;&#34;&#34;Return a list of all saved AI sessions.&#34;&#34;&#34;
from connpy.ai import ai
agent = ai(self.config)
return agent._get_sessions()
def delete_session(self, session_id):
&#34;&#34;&#34;Delete an AI session by ID.&#34;&#34;&#34;
import os
sessions_dir = os.path.join(self.config.defaultdir, &#34;ai_sessions&#34;)
path = os.path.join(sessions_dir, f&#34;{session_id}.json&#34;)
if os.path.exists(path):
os.remove(path)
else:
raise InvalidConfigurationError(f&#34;Session &#39;{session_id}&#39; not found.&#34;)
def configure_provider(self, provider, model=None, api_key=None):
&#34;&#34;&#34;Update AI provider settings in the configuration.&#34;&#34;&#34;
settings = self.config.config.get(&#34;ai&#34;, {})
if model:
settings[f&#34;{provider}_model&#34;] = model
if api_key:
settings[f&#34;{provider}_api_key&#34;] = api_key
self.config.config[&#34;ai&#34;] = settings
self.config._saveconfig(self.config.file)
def load_session_data(self, session_id):
&#34;&#34;&#34;Load a session&#39;s raw data by ID.&#34;&#34;&#34;
from connpy.ai import ai
agent = ai(self.config)
return agent.load_session_data(session_id)</code></pre>
</details>
<div class="desc"><p>Business logic for interacting with AI agents and LLM configurations.</p>
<p>Initialize the service.</p>
<h2 id="args">Args</h2>
<dl>
<dt><strong><code>config</code></strong></dt>
<dd>An instance of configfile (or None to instantiate a new one/use global context).</dd>
</dl></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li><a title="connpy.services.base.BaseService" href="base.html#connpy.services.base.BaseService">BaseService</a></li>
</ul>
<h3>Methods</h3>
<dl>
<dt id="connpy.services.ai_service.AIService.ask"><code class="name flex">
<span>def <span class="ident">ask</span></span>(<span>self,<br>input_text,<br>dryrun=False,<br>chat_history=None,<br>status=None,<br>debug=False,<br>session_id=None,<br>console=None,<br>chunk_callback=None,<br>confirm_handler=None,<br>trust=False,<br>**overrides)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def ask(self, input_text, dryrun=False, chat_history=None, status=None, debug=False, session_id=None, console=None, chunk_callback=None, confirm_handler=None, trust=False, **overrides):
&#34;&#34;&#34;Send a prompt to the AI agent.&#34;&#34;&#34;
from connpy.ai import ai
agent = ai(self.config, console=console, confirm_handler=confirm_handler, trust=trust, **overrides)
return agent.ask(input_text, dryrun, chat_history, status=status, debug=debug, session_id=session_id, chunk_callback=chunk_callback)</code></pre>
</details>
<div class="desc"><p>Send a prompt to the AI agent.</p></div>
</dd>
<dt id="connpy.services.ai_service.AIService.configure_provider"><code class="name flex">
<span>def <span class="ident">configure_provider</span></span>(<span>self, provider, model=None, api_key=None)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def configure_provider(self, provider, model=None, api_key=None):
&#34;&#34;&#34;Update AI provider settings in the configuration.&#34;&#34;&#34;
settings = self.config.config.get(&#34;ai&#34;, {})
if model:
settings[f&#34;{provider}_model&#34;] = model
if api_key:
settings[f&#34;{provider}_api_key&#34;] = api_key
self.config.config[&#34;ai&#34;] = settings
self.config._saveconfig(self.config.file)</code></pre>
</details>
<div class="desc"><p>Update AI provider settings in the configuration.</p></div>
</dd>
<dt id="connpy.services.ai_service.AIService.confirm"><code class="name flex">
<span>def <span class="ident">confirm</span></span>(<span>self, input_text, console=None)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def confirm(self, input_text, console=None):
&#34;&#34;&#34;Ask for a safe confirmation of an action.&#34;&#34;&#34;
from connpy.ai import ai
agent = ai(self.config, console=console)
return agent.confirm(input_text)</code></pre>
</details>
<div class="desc"><p>Ask for a safe confirmation of an action.</p></div>
</dd>
<dt id="connpy.services.ai_service.AIService.delete_session"><code class="name flex">
<span>def <span class="ident">delete_session</span></span>(<span>self, session_id)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def delete_session(self, session_id):
&#34;&#34;&#34;Delete an AI session by ID.&#34;&#34;&#34;
import os
sessions_dir = os.path.join(self.config.defaultdir, &#34;ai_sessions&#34;)
path = os.path.join(sessions_dir, f&#34;{session_id}.json&#34;)
if os.path.exists(path):
os.remove(path)
else:
raise InvalidConfigurationError(f&#34;Session &#39;{session_id}&#39; not found.&#34;)</code></pre>
</details>
<div class="desc"><p>Delete an AI session by ID.</p></div>
</dd>
<dt id="connpy.services.ai_service.AIService.list_sessions"><code class="name flex">
<span>def <span class="ident">list_sessions</span></span>(<span>self)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def list_sessions(self):
&#34;&#34;&#34;Return a list of all saved AI sessions.&#34;&#34;&#34;
from connpy.ai import ai
agent = ai(self.config)
return agent._get_sessions()</code></pre>
</details>
<div class="desc"><p>Return a list of all saved AI sessions.</p></div>
</dd>
<dt id="connpy.services.ai_service.AIService.load_session_data"><code class="name flex">
<span>def <span class="ident">load_session_data</span></span>(<span>self, session_id)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def load_session_data(self, session_id):
&#34;&#34;&#34;Load a session&#39;s raw data by ID.&#34;&#34;&#34;
from connpy.ai import ai
agent = ai(self.config)
return agent.load_session_data(session_id)</code></pre>
</details>
<div class="desc"><p>Load a session's raw data by ID.</p></div>
</dd>
</dl>
<h3>Inherited members</h3>
<ul class="hlist">
<li><code><b><a title="connpy.services.base.BaseService" href="base.html#connpy.services.base.BaseService">BaseService</a></b></code>:
<ul class="hlist">
<li><code><a title="connpy.services.base.BaseService.set_reserved_names" href="base.html#connpy.services.base.BaseService.set_reserved_names">set_reserved_names</a></code></li>
</ul>
</li>
</ul>
</dd>
</dl>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="connpy.services" href="index.html">connpy.services</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="connpy.services.ai_service.AIService" href="#connpy.services.ai_service.AIService">AIService</a></code></h4>
<ul class="two-column">
<li><code><a title="connpy.services.ai_service.AIService.ask" href="#connpy.services.ai_service.AIService.ask">ask</a></code></li>
<li><code><a title="connpy.services.ai_service.AIService.configure_provider" href="#connpy.services.ai_service.AIService.configure_provider">configure_provider</a></code></li>
<li><code><a title="connpy.services.ai_service.AIService.confirm" href="#connpy.services.ai_service.AIService.confirm">confirm</a></code></li>
<li><code><a title="connpy.services.ai_service.AIService.delete_session" href="#connpy.services.ai_service.AIService.delete_session">delete_session</a></code></li>
<li><code><a title="connpy.services.ai_service.AIService.list_sessions" href="#connpy.services.ai_service.AIService.list_sessions">list_sessions</a></code></li>
<li><code><a title="connpy.services.ai_service.AIService.load_session_data" href="#connpy.services.ai_service.AIService.load_session_data">load_session_data</a></code></li>
</ul>
</li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.5</a>.</p>
</footer>
</body>
</html>
+158
View File
@@ -0,0 +1,158 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.5">
<title>connpy.services.base API documentation</title>
<meta name="description" content="">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source > summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible;min-width:max-content}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin:1em 0}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
/* Collapse source docstrings */
setTimeout(() => {
[...document.querySelectorAll('.hljs.language-python > .hljs-string')]
.filter(el => el.innerHTML.length > 200 && ['"""', "'''"].includes(el.innerHTML.substring(0, 3)))
.forEach(el => {
let d = document.createElement('details');
d.classList.add('hljs-string');
d.innerHTML = '<summary>"""</summary>' + el.innerHTML.substring(3);
el.replaceWith(d);
});
}, 100);
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>connpy.services.base</code></h1>
</header>
<section id="section-intro">
</section>
<section>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="connpy.services.base.BaseService"><code class="flex name class">
<span>class <span class="ident">BaseService</span></span>
<span>(</span><span>config=None)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class BaseService:
&#34;&#34;&#34;Base class for all connpy services, providing common configuration access.&#34;&#34;&#34;
def __init__(self, config=None):
&#34;&#34;&#34;
Initialize the service.
Args:
config: An instance of configfile (or None to instantiate a new one/use global context).
&#34;&#34;&#34;
from connpy import configfile
self.config = config or configfile()
self.hooks = MethodHook
self.reserved_names = []
def set_reserved_names(self, names):
&#34;&#34;&#34;Inject a list of reserved names (e.g. from the CLI).&#34;&#34;&#34;
self.reserved_names = names
def _validate_node_name(self, unique_id):
&#34;&#34;&#34;Check if the node name in unique_id is reserved.&#34;&#34;&#34;
from .exceptions import ReservedNameError
if not self.reserved_names:
return
uniques = self.config._explode_unique(unique_id)
if uniques and &#34;id&#34; in uniques:
# We only validate the &#39;id&#39; (the actual node name), folders are prefixed with @
node_name = uniques[&#34;id&#34;]
if node_name in self.reserved_names:
raise ReservedNameError(f&#34;Node name &#39;{node_name}&#39; is a reserved command.&#34;)</code></pre>
</details>
<div class="desc"><p>Base class for all connpy services, providing common configuration access.</p>
<p>Initialize the service.</p>
<h2 id="args">Args</h2>
<dl>
<dt><strong><code>config</code></strong></dt>
<dd>An instance of configfile (or None to instantiate a new one/use global context).</dd>
</dl></div>
<h3>Subclasses</h3>
<ul class="hlist">
<li><a title="connpy.services.ai_service.AIService" href="ai_service.html#connpy.services.ai_service.AIService">AIService</a></li>
<li><a title="connpy.services.config_service.ConfigService" href="config_service.html#connpy.services.config_service.ConfigService">ConfigService</a></li>
<li><a title="connpy.services.context_service.ContextService" href="context_service.html#connpy.services.context_service.ContextService">ContextService</a></li>
<li><a title="connpy.services.execution_service.ExecutionService" href="execution_service.html#connpy.services.execution_service.ExecutionService">ExecutionService</a></li>
<li><a title="connpy.services.import_export_service.ImportExportService" href="import_export_service.html#connpy.services.import_export_service.ImportExportService">ImportExportService</a></li>
<li><a title="connpy.services.node_service.NodeService" href="node_service.html#connpy.services.node_service.NodeService">NodeService</a></li>
<li><a title="connpy.services.plugin_service.PluginService" href="plugin_service.html#connpy.services.plugin_service.PluginService">PluginService</a></li>
<li><a title="connpy.services.profile_service.ProfileService" href="profile_service.html#connpy.services.profile_service.ProfileService">ProfileService</a></li>
<li><a title="connpy.services.sync_service.SyncService" href="sync_service.html#connpy.services.sync_service.SyncService">SyncService</a></li>
<li><a title="connpy.services.system_service.SystemService" href="system_service.html#connpy.services.system_service.SystemService">SystemService</a></li>
</ul>
<h3>Methods</h3>
<dl>
<dt id="connpy.services.base.BaseService.set_reserved_names"><code class="name flex">
<span>def <span class="ident">set_reserved_names</span></span>(<span>self, names)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def set_reserved_names(self, names):
&#34;&#34;&#34;Inject a list of reserved names (e.g. from the CLI).&#34;&#34;&#34;
self.reserved_names = names</code></pre>
</details>
<div class="desc"><p>Inject a list of reserved names (e.g. from the CLI).</p></div>
</dd>
</dl>
</dd>
</dl>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="connpy.services" href="index.html">connpy.services</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="connpy.services.base.BaseService" href="#connpy.services.base.BaseService">BaseService</a></code></h4>
<ul class="">
<li><code><a title="connpy.services.base.BaseService.set_reserved_names" href="#connpy.services.base.BaseService.set_reserved_names">set_reserved_names</a></code></li>
</ul>
</li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.5</a>.</p>
</footer>
</body>
</html>
+317
View File
@@ -0,0 +1,317 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.5">
<title>connpy.services.config_service API documentation</title>
<meta name="description" content="">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source > summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible;min-width:max-content}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin:1em 0}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
/* Collapse source docstrings */
setTimeout(() => {
[...document.querySelectorAll('.hljs.language-python > .hljs-string')]
.filter(el => el.innerHTML.length > 200 && ['"""', "'''"].includes(el.innerHTML.substring(0, 3)))
.forEach(el => {
let d = document.createElement('details');
d.classList.add('hljs-string');
d.innerHTML = '<summary>"""</summary>' + el.innerHTML.substring(3);
el.replaceWith(d);
});
}, 100);
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>connpy.services.config_service</code></h1>
</header>
<section id="section-intro">
</section>
<section>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="connpy.services.config_service.ConfigService"><code class="flex name class">
<span>class <span class="ident">ConfigService</span></span>
<span>(</span><span>config=None)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class ConfigService(BaseService):
&#34;&#34;&#34;Business logic for general application settings and state configuration.&#34;&#34;&#34;
def get_settings(self) -&gt; Dict[str, Any]:
&#34;&#34;&#34;Get the global configuration settings block.&#34;&#34;&#34;
settings = self.config.config.copy()
settings[&#34;configfolder&#34;] = self.config.defaultdir
return settings
def get_default_dir(self) -&gt; str:
&#34;&#34;&#34;Get the default configuration directory.&#34;&#34;&#34;
return self.config.defaultdir
def set_config_folder(self, folder_path: str):
&#34;&#34;&#34;Set the default location for config file by writing to ~/.config/conn/.folder&#34;&#34;&#34;
if not os.path.isdir(folder_path):
raise ConnpyError(f&#34;readable_dir:{folder_path} is not a valid path&#34;)
pathfile = os.path.join(self.config.anchor_path, &#34;.folder&#34;)
folder = os.path.abspath(folder_path).rstrip(&#39;/&#39;)
try:
with open(pathfile, &#34;w&#34;) as f:
f.write(str(folder))
except Exception as e:
raise ConnpyError(f&#34;Failed to save config folder: {e}&#34;)
def update_setting(self, key, value):
&#34;&#34;&#34;Update a setting in the configuration file.&#34;&#34;&#34;
self.config.config[key] = value
self.config._saveconfig(self.config.file)
def encrypt_password(self, password):
&#34;&#34;&#34;Encrypt a password using the application&#39;s configuration encryption key.&#34;&#34;&#34;
return self.config.encrypt(password)
def apply_theme_from_file(self, theme_input):
&#34;&#34;&#34;Apply &#39;dark&#39;, &#39;light&#39; theme or load a YAML theme file and save it to the configuration.&#34;&#34;&#34;
import yaml
from ..printer import STYLES, LIGHT_THEME
if theme_input == &#34;dark&#34;:
valid_styles = {}
self.update_setting(&#34;theme&#34;, valid_styles)
return valid_styles
elif theme_input == &#34;light&#34;:
valid_styles = LIGHT_THEME.copy()
self.update_setting(&#34;theme&#34;, valid_styles)
return valid_styles
if not os.path.exists(theme_input):
raise InvalidConfigurationError(f&#34;Theme file &#39;{theme_input}&#39; not found.&#34;)
try:
with open(theme_input, &#39;r&#39;) as f:
user_styles = yaml.safe_load(f)
except Exception as e:
raise InvalidConfigurationError(f&#34;Failed to parse theme file: {e}&#34;)
if not isinstance(user_styles, dict):
raise InvalidConfigurationError(&#34;Theme file must be a YAML dictionary.&#34;)
# Filter for valid styles only (prevent junk in config)
valid_styles = {k: v for k, v in user_styles.items() if k in STYLES}
if not valid_styles:
raise InvalidConfigurationError(&#34;No valid style keys found in theme file.&#34;)
# Persist and return merged styles
self.update_setting(&#34;theme&#34;, valid_styles)
return valid_styles</code></pre>
</details>
<div class="desc"><p>Business logic for general application settings and state configuration.</p>
<p>Initialize the service.</p>
<h2 id="args">Args</h2>
<dl>
<dt><strong><code>config</code></strong></dt>
<dd>An instance of configfile (or None to instantiate a new one/use global context).</dd>
</dl></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li><a title="connpy.services.base.BaseService" href="base.html#connpy.services.base.BaseService">BaseService</a></li>
</ul>
<h3>Methods</h3>
<dl>
<dt id="connpy.services.config_service.ConfigService.apply_theme_from_file"><code class="name flex">
<span>def <span class="ident">apply_theme_from_file</span></span>(<span>self, theme_input)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def apply_theme_from_file(self, theme_input):
&#34;&#34;&#34;Apply &#39;dark&#39;, &#39;light&#39; theme or load a YAML theme file and save it to the configuration.&#34;&#34;&#34;
import yaml
from ..printer import STYLES, LIGHT_THEME
if theme_input == &#34;dark&#34;:
valid_styles = {}
self.update_setting(&#34;theme&#34;, valid_styles)
return valid_styles
elif theme_input == &#34;light&#34;:
valid_styles = LIGHT_THEME.copy()
self.update_setting(&#34;theme&#34;, valid_styles)
return valid_styles
if not os.path.exists(theme_input):
raise InvalidConfigurationError(f&#34;Theme file &#39;{theme_input}&#39; not found.&#34;)
try:
with open(theme_input, &#39;r&#39;) as f:
user_styles = yaml.safe_load(f)
except Exception as e:
raise InvalidConfigurationError(f&#34;Failed to parse theme file: {e}&#34;)
if not isinstance(user_styles, dict):
raise InvalidConfigurationError(&#34;Theme file must be a YAML dictionary.&#34;)
# Filter for valid styles only (prevent junk in config)
valid_styles = {k: v for k, v in user_styles.items() if k in STYLES}
if not valid_styles:
raise InvalidConfigurationError(&#34;No valid style keys found in theme file.&#34;)
# Persist and return merged styles
self.update_setting(&#34;theme&#34;, valid_styles)
return valid_styles</code></pre>
</details>
<div class="desc"><p>Apply 'dark', 'light' theme or load a YAML theme file and save it to the configuration.</p></div>
</dd>
<dt id="connpy.services.config_service.ConfigService.encrypt_password"><code class="name flex">
<span>def <span class="ident">encrypt_password</span></span>(<span>self, password)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def encrypt_password(self, password):
&#34;&#34;&#34;Encrypt a password using the application&#39;s configuration encryption key.&#34;&#34;&#34;
return self.config.encrypt(password)</code></pre>
</details>
<div class="desc"><p>Encrypt a password using the application's configuration encryption key.</p></div>
</dd>
<dt id="connpy.services.config_service.ConfigService.get_default_dir"><code class="name flex">
<span>def <span class="ident">get_default_dir</span></span>(<span>self) > str</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def get_default_dir(self) -&gt; str:
&#34;&#34;&#34;Get the default configuration directory.&#34;&#34;&#34;
return self.config.defaultdir</code></pre>
</details>
<div class="desc"><p>Get the default configuration directory.</p></div>
</dd>
<dt id="connpy.services.config_service.ConfigService.get_settings"><code class="name flex">
<span>def <span class="ident">get_settings</span></span>(<span>self) > Dict[str, Any]</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def get_settings(self) -&gt; Dict[str, Any]:
&#34;&#34;&#34;Get the global configuration settings block.&#34;&#34;&#34;
settings = self.config.config.copy()
settings[&#34;configfolder&#34;] = self.config.defaultdir
return settings</code></pre>
</details>
<div class="desc"><p>Get the global configuration settings block.</p></div>
</dd>
<dt id="connpy.services.config_service.ConfigService.set_config_folder"><code class="name flex">
<span>def <span class="ident">set_config_folder</span></span>(<span>self, folder_path: str)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def set_config_folder(self, folder_path: str):
&#34;&#34;&#34;Set the default location for config file by writing to ~/.config/conn/.folder&#34;&#34;&#34;
if not os.path.isdir(folder_path):
raise ConnpyError(f&#34;readable_dir:{folder_path} is not a valid path&#34;)
pathfile = os.path.join(self.config.anchor_path, &#34;.folder&#34;)
folder = os.path.abspath(folder_path).rstrip(&#39;/&#39;)
try:
with open(pathfile, &#34;w&#34;) as f:
f.write(str(folder))
except Exception as e:
raise ConnpyError(f&#34;Failed to save config folder: {e}&#34;)</code></pre>
</details>
<div class="desc"><p>Set the default location for config file by writing to ~/.config/conn/.folder</p></div>
</dd>
<dt id="connpy.services.config_service.ConfigService.update_setting"><code class="name flex">
<span>def <span class="ident">update_setting</span></span>(<span>self, key, value)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def update_setting(self, key, value):
&#34;&#34;&#34;Update a setting in the configuration file.&#34;&#34;&#34;
self.config.config[key] = value
self.config._saveconfig(self.config.file)</code></pre>
</details>
<div class="desc"><p>Update a setting in the configuration file.</p></div>
</dd>
</dl>
<h3>Inherited members</h3>
<ul class="hlist">
<li><code><b><a title="connpy.services.base.BaseService" href="base.html#connpy.services.base.BaseService">BaseService</a></b></code>:
<ul class="hlist">
<li><code><a title="connpy.services.base.BaseService.set_reserved_names" href="base.html#connpy.services.base.BaseService.set_reserved_names">set_reserved_names</a></code></li>
</ul>
</li>
</ul>
</dd>
</dl>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="connpy.services" href="index.html">connpy.services</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="connpy.services.config_service.ConfigService" href="#connpy.services.config_service.ConfigService">ConfigService</a></code></h4>
<ul class="">
<li><code><a title="connpy.services.config_service.ConfigService.apply_theme_from_file" href="#connpy.services.config_service.ConfigService.apply_theme_from_file">apply_theme_from_file</a></code></li>
<li><code><a title="connpy.services.config_service.ConfigService.encrypt_password" href="#connpy.services.config_service.ConfigService.encrypt_password">encrypt_password</a></code></li>
<li><code><a title="connpy.services.config_service.ConfigService.get_default_dir" href="#connpy.services.config_service.ConfigService.get_default_dir">get_default_dir</a></code></li>
<li><code><a title="connpy.services.config_service.ConfigService.get_settings" href="#connpy.services.config_service.ConfigService.get_settings">get_settings</a></code></li>
<li><code><a title="connpy.services.config_service.ConfigService.set_config_folder" href="#connpy.services.config_service.ConfigService.set_config_folder">set_config_folder</a></code></li>
<li><code><a title="connpy.services.config_service.ConfigService.update_setting" href="#connpy.services.config_service.ConfigService.update_setting">update_setting</a></code></li>
</ul>
</li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.5</a>.</p>
</footer>
</body>
</html>
+376
View File
@@ -0,0 +1,376 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.5">
<title>connpy.services.context_service API documentation</title>
<meta name="description" content="">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source > summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible;min-width:max-content}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin:1em 0}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
/* Collapse source docstrings */
setTimeout(() => {
[...document.querySelectorAll('.hljs.language-python > .hljs-string')]
.filter(el => el.innerHTML.length > 200 && ['"""', "'''"].includes(el.innerHTML.substring(0, 3)))
.forEach(el => {
let d = document.createElement('details');
d.classList.add('hljs-string');
d.innerHTML = '<summary>"""</summary>' + el.innerHTML.substring(3);
el.replaceWith(d);
});
}, 100);
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>connpy.services.context_service</code></h1>
</header>
<section id="section-intro">
</section>
<section>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="connpy.services.context_service.ContextService"><code class="flex name class">
<span>class <span class="ident">ContextService</span></span>
<span>(</span><span>config=None)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class ContextService(BaseService):
&#34;&#34;&#34;Business logic for managing and applying regex-based contexts locally.&#34;&#34;&#34;
@property
def contexts(self) -&gt; Dict[str, List[str]]:
return self.config.config.get(&#34;contexts&#34;, {&#34;all&#34;: [&#34;.*&#34;]})
@property
def current_context(self) -&gt; str:
return self.config.config.get(&#34;current_context&#34;, &#34;all&#34;)
def list_contexts(self) -&gt; List[Dict[str, Any]]:
result = []
for name in self.contexts.keys():
result.append({
&#34;name&#34;: name,
&#34;active&#34;: (name == self.current_context),
&#34;regexes&#34;: self.contexts[name]
})
return result
def add_context(self, name: str, regexes: List[str]):
if not name.isalnum():
raise ValueError(&#34;Context name must be alphanumeric&#34;)
ctxs = self.contexts
if name in ctxs:
raise ValueError(f&#34;Context &#39;{name}&#39; already exists&#34;)
ctxs[name] = regexes
self.config.config[&#34;contexts&#34;] = ctxs
self.config._saveconfig(self.config.file)
def update_context(self, name: str, regexes: List[str]):
if name == &#34;all&#34;:
raise ValueError(&#34;Cannot modify default context &#39;all&#39;&#34;)
ctxs = self.contexts
if name not in ctxs:
raise ValueError(f&#34;Context &#39;{name}&#39; does not exist&#34;)
ctxs[name] = regexes
self.config.config[&#34;contexts&#34;] = ctxs
self.config._saveconfig(self.config.file)
def delete_context(self, name: str):
if name == &#34;all&#34;:
raise ValueError(&#34;Cannot delete default context &#39;all&#39;&#34;)
if name == self.current_context:
raise ValueError(f&#34;Cannot delete active context &#39;{name}&#39;&#34;)
ctxs = self.contexts
if name not in ctxs:
raise ValueError(f&#34;Context &#39;{name}&#39; does not exist&#34;)
del ctxs[name]
self.config.config[&#34;contexts&#34;] = ctxs
self.config._saveconfig(self.config.file)
def set_active_context(self, name: str):
if name not in self.contexts:
raise ValueError(f&#34;Context &#39;{name}&#39; does not exist&#34;)
self.config.config[&#34;current_context&#34;] = name
self.config._saveconfig(self.config.file)
def get_active_regexes(self) -&gt; List[re.Pattern]:
patterns = self.contexts.get(self.current_context, [&#34;.*&#34;])
return [re.compile(p) for p in patterns]
def _match_any(self, node_name: str, patterns: List[re.Pattern]) -&gt; bool:
return any(p.match(node_name) for p in patterns)
# Hook handlers for filtering
def filter_node_list(self, *args, **kwargs):
patterns = self.get_active_regexes()
return [node for node in kwargs[&#34;result&#34;] if self._match_any(node, patterns)]
def filter_node_dict(self, *args, **kwargs):
patterns = self.get_active_regexes()
return {k: v for k, v in kwargs[&#34;result&#34;].items() if self._match_any(k, patterns)}</code></pre>
</details>
<div class="desc"><p>Business logic for managing and applying regex-based contexts locally.</p>
<p>Initialize the service.</p>
<h2 id="args">Args</h2>
<dl>
<dt><strong><code>config</code></strong></dt>
<dd>An instance of configfile (or None to instantiate a new one/use global context).</dd>
</dl></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li><a title="connpy.services.base.BaseService" href="base.html#connpy.services.base.BaseService">BaseService</a></li>
</ul>
<h3>Instance variables</h3>
<dl>
<dt id="connpy.services.context_service.ContextService.contexts"><code class="name">prop <span class="ident">contexts</span> : Dict[str, List[str]]</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@property
def contexts(self) -&gt; Dict[str, List[str]]:
return self.config.config.get(&#34;contexts&#34;, {&#34;all&#34;: [&#34;.*&#34;]})</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.services.context_service.ContextService.current_context"><code class="name">prop <span class="ident">current_context</span> : str</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@property
def current_context(self) -&gt; str:
return self.config.config.get(&#34;current_context&#34;, &#34;all&#34;)</code></pre>
</details>
<div class="desc"></div>
</dd>
</dl>
<h3>Methods</h3>
<dl>
<dt id="connpy.services.context_service.ContextService.add_context"><code class="name flex">
<span>def <span class="ident">add_context</span></span>(<span>self, name: str, regexes: List[str])</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def add_context(self, name: str, regexes: List[str]):
if not name.isalnum():
raise ValueError(&#34;Context name must be alphanumeric&#34;)
ctxs = self.contexts
if name in ctxs:
raise ValueError(f&#34;Context &#39;{name}&#39; already exists&#34;)
ctxs[name] = regexes
self.config.config[&#34;contexts&#34;] = ctxs
self.config._saveconfig(self.config.file)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.services.context_service.ContextService.delete_context"><code class="name flex">
<span>def <span class="ident">delete_context</span></span>(<span>self, name: str)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def delete_context(self, name: str):
if name == &#34;all&#34;:
raise ValueError(&#34;Cannot delete default context &#39;all&#39;&#34;)
if name == self.current_context:
raise ValueError(f&#34;Cannot delete active context &#39;{name}&#39;&#34;)
ctxs = self.contexts
if name not in ctxs:
raise ValueError(f&#34;Context &#39;{name}&#39; does not exist&#34;)
del ctxs[name]
self.config.config[&#34;contexts&#34;] = ctxs
self.config._saveconfig(self.config.file)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.services.context_service.ContextService.filter_node_dict"><code class="name flex">
<span>def <span class="ident">filter_node_dict</span></span>(<span>self, *args, **kwargs)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def filter_node_dict(self, *args, **kwargs):
patterns = self.get_active_regexes()
return {k: v for k, v in kwargs[&#34;result&#34;].items() if self._match_any(k, patterns)}</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.services.context_service.ContextService.filter_node_list"><code class="name flex">
<span>def <span class="ident">filter_node_list</span></span>(<span>self, *args, **kwargs)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def filter_node_list(self, *args, **kwargs):
patterns = self.get_active_regexes()
return [node for node in kwargs[&#34;result&#34;] if self._match_any(node, patterns)]</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.services.context_service.ContextService.get_active_regexes"><code class="name flex">
<span>def <span class="ident">get_active_regexes</span></span>(<span>self) > List[re.Pattern]</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def get_active_regexes(self) -&gt; List[re.Pattern]:
patterns = self.contexts.get(self.current_context, [&#34;.*&#34;])
return [re.compile(p) for p in patterns]</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.services.context_service.ContextService.list_contexts"><code class="name flex">
<span>def <span class="ident">list_contexts</span></span>(<span>self) > List[Dict[str, Any]]</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def list_contexts(self) -&gt; List[Dict[str, Any]]:
result = []
for name in self.contexts.keys():
result.append({
&#34;name&#34;: name,
&#34;active&#34;: (name == self.current_context),
&#34;regexes&#34;: self.contexts[name]
})
return result</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.services.context_service.ContextService.set_active_context"><code class="name flex">
<span>def <span class="ident">set_active_context</span></span>(<span>self, name: str)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def set_active_context(self, name: str):
if name not in self.contexts:
raise ValueError(f&#34;Context &#39;{name}&#39; does not exist&#34;)
self.config.config[&#34;current_context&#34;] = name
self.config._saveconfig(self.config.file)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.services.context_service.ContextService.update_context"><code class="name flex">
<span>def <span class="ident">update_context</span></span>(<span>self, name: str, regexes: List[str])</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def update_context(self, name: str, regexes: List[str]):
if name == &#34;all&#34;:
raise ValueError(&#34;Cannot modify default context &#39;all&#39;&#34;)
ctxs = self.contexts
if name not in ctxs:
raise ValueError(f&#34;Context &#39;{name}&#39; does not exist&#34;)
ctxs[name] = regexes
self.config.config[&#34;contexts&#34;] = ctxs
self.config._saveconfig(self.config.file)</code></pre>
</details>
<div class="desc"></div>
</dd>
</dl>
<h3>Inherited members</h3>
<ul class="hlist">
<li><code><b><a title="connpy.services.base.BaseService" href="base.html#connpy.services.base.BaseService">BaseService</a></b></code>:
<ul class="hlist">
<li><code><a title="connpy.services.base.BaseService.set_reserved_names" href="base.html#connpy.services.base.BaseService.set_reserved_names">set_reserved_names</a></code></li>
</ul>
</li>
</ul>
</dd>
</dl>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="connpy.services" href="index.html">connpy.services</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="connpy.services.context_service.ContextService" href="#connpy.services.context_service.ContextService">ContextService</a></code></h4>
<ul class="two-column">
<li><code><a title="connpy.services.context_service.ContextService.add_context" href="#connpy.services.context_service.ContextService.add_context">add_context</a></code></li>
<li><code><a title="connpy.services.context_service.ContextService.contexts" href="#connpy.services.context_service.ContextService.contexts">contexts</a></code></li>
<li><code><a title="connpy.services.context_service.ContextService.current_context" href="#connpy.services.context_service.ContextService.current_context">current_context</a></code></li>
<li><code><a title="connpy.services.context_service.ContextService.delete_context" href="#connpy.services.context_service.ContextService.delete_context">delete_context</a></code></li>
<li><code><a title="connpy.services.context_service.ContextService.filter_node_dict" href="#connpy.services.context_service.ContextService.filter_node_dict">filter_node_dict</a></code></li>
<li><code><a title="connpy.services.context_service.ContextService.filter_node_list" href="#connpy.services.context_service.ContextService.filter_node_list">filter_node_list</a></code></li>
<li><code><a title="connpy.services.context_service.ContextService.get_active_regexes" href="#connpy.services.context_service.ContextService.get_active_regexes">get_active_regexes</a></code></li>
<li><code><a title="connpy.services.context_service.ContextService.list_contexts" href="#connpy.services.context_service.ContextService.list_contexts">list_contexts</a></code></li>
<li><code><a title="connpy.services.context_service.ContextService.set_active_context" href="#connpy.services.context_service.ContextService.set_active_context">set_active_context</a></code></li>
<li><code><a title="connpy.services.context_service.ContextService.update_context" href="#connpy.services.context_service.ContextService.update_context">update_context</a></code></li>
</ul>
</li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.5</a>.</p>
</footer>
</body>
</html>
+274
View File
@@ -0,0 +1,274 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.5">
<title>connpy.services.exceptions API documentation</title>
<meta name="description" content="">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source > summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible;min-width:max-content}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin:1em 0}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
/* Collapse source docstrings */
setTimeout(() => {
[...document.querySelectorAll('.hljs.language-python > .hljs-string')]
.filter(el => el.innerHTML.length > 200 && ['"""', "'''"].includes(el.innerHTML.substring(0, 3)))
.forEach(el => {
let d = document.createElement('details');
d.classList.add('hljs-string');
d.innerHTML = '<summary>"""</summary>' + el.innerHTML.substring(3);
el.replaceWith(d);
});
}, 100);
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>connpy.services.exceptions</code></h1>
</header>
<section id="section-intro">
</section>
<section>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="connpy.services.exceptions.ConnpyError"><code class="flex name class">
<span>class <span class="ident">ConnpyError</span></span>
<span>(</span><span>*args, **kwargs)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class ConnpyError(Exception):
&#34;&#34;&#34;Base exception for all connpy services.&#34;&#34;&#34;
pass</code></pre>
</details>
<div class="desc"><p>Base exception for all connpy services.</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li>builtins.Exception</li>
<li>builtins.BaseException</li>
</ul>
<h3>Subclasses</h3>
<ul class="hlist">
<li><a title="connpy.services.exceptions.ExecutionError" href="#connpy.services.exceptions.ExecutionError">ExecutionError</a></li>
<li><a title="connpy.services.exceptions.InvalidConfigurationError" href="#connpy.services.exceptions.InvalidConfigurationError">InvalidConfigurationError</a></li>
<li><a title="connpy.services.exceptions.NodeAlreadyExistsError" href="#connpy.services.exceptions.NodeAlreadyExistsError">NodeAlreadyExistsError</a></li>
<li><a title="connpy.services.exceptions.NodeNotFoundError" href="#connpy.services.exceptions.NodeNotFoundError">NodeNotFoundError</a></li>
<li><a title="connpy.services.exceptions.ProfileAlreadyExistsError" href="#connpy.services.exceptions.ProfileAlreadyExistsError">ProfileAlreadyExistsError</a></li>
<li><a title="connpy.services.exceptions.ProfileNotFoundError" href="#connpy.services.exceptions.ProfileNotFoundError">ProfileNotFoundError</a></li>
<li><a title="connpy.services.exceptions.ReservedNameError" href="#connpy.services.exceptions.ReservedNameError">ReservedNameError</a></li>
</ul>
</dd>
<dt id="connpy.services.exceptions.ExecutionError"><code class="flex name class">
<span>class <span class="ident">ExecutionError</span></span>
<span>(</span><span>*args, **kwargs)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class ExecutionError(ConnpyError):
&#34;&#34;&#34;Raised when an execution fails or returns error.&#34;&#34;&#34;
pass</code></pre>
</details>
<div class="desc"><p>Raised when an execution fails or returns error.</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li><a title="connpy.services.exceptions.ConnpyError" href="#connpy.services.exceptions.ConnpyError">ConnpyError</a></li>
<li>builtins.Exception</li>
<li>builtins.BaseException</li>
</ul>
</dd>
<dt id="connpy.services.exceptions.InvalidConfigurationError"><code class="flex name class">
<span>class <span class="ident">InvalidConfigurationError</span></span>
<span>(</span><span>*args, **kwargs)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class InvalidConfigurationError(ConnpyError):
&#34;&#34;&#34;Raised when data or configuration input is invalid.&#34;&#34;&#34;
pass</code></pre>
</details>
<div class="desc"><p>Raised when data or configuration input is invalid.</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li><a title="connpy.services.exceptions.ConnpyError" href="#connpy.services.exceptions.ConnpyError">ConnpyError</a></li>
<li>builtins.Exception</li>
<li>builtins.BaseException</li>
</ul>
</dd>
<dt id="connpy.services.exceptions.NodeAlreadyExistsError"><code class="flex name class">
<span>class <span class="ident">NodeAlreadyExistsError</span></span>
<span>(</span><span>*args, **kwargs)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class NodeAlreadyExistsError(ConnpyError):
&#34;&#34;&#34;Raised when a node or folder already exists.&#34;&#34;&#34;
pass</code></pre>
</details>
<div class="desc"><p>Raised when a node or folder already exists.</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li><a title="connpy.services.exceptions.ConnpyError" href="#connpy.services.exceptions.ConnpyError">ConnpyError</a></li>
<li>builtins.Exception</li>
<li>builtins.BaseException</li>
</ul>
</dd>
<dt id="connpy.services.exceptions.NodeNotFoundError"><code class="flex name class">
<span>class <span class="ident">NodeNotFoundError</span></span>
<span>(</span><span>*args, **kwargs)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class NodeNotFoundError(ConnpyError):
&#34;&#34;&#34;Raised when a connection or folder is not found.&#34;&#34;&#34;
pass</code></pre>
</details>
<div class="desc"><p>Raised when a connection or folder is not found.</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li><a title="connpy.services.exceptions.ConnpyError" href="#connpy.services.exceptions.ConnpyError">ConnpyError</a></li>
<li>builtins.Exception</li>
<li>builtins.BaseException</li>
</ul>
</dd>
<dt id="connpy.services.exceptions.ProfileAlreadyExistsError"><code class="flex name class">
<span>class <span class="ident">ProfileAlreadyExistsError</span></span>
<span>(</span><span>*args, **kwargs)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class ProfileAlreadyExistsError(ConnpyError):
&#34;&#34;&#34;Raised when a profile with the same name already exists.&#34;&#34;&#34;
pass</code></pre>
</details>
<div class="desc"><p>Raised when a profile with the same name already exists.</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li><a title="connpy.services.exceptions.ConnpyError" href="#connpy.services.exceptions.ConnpyError">ConnpyError</a></li>
<li>builtins.Exception</li>
<li>builtins.BaseException</li>
</ul>
</dd>
<dt id="connpy.services.exceptions.ProfileNotFoundError"><code class="flex name class">
<span>class <span class="ident">ProfileNotFoundError</span></span>
<span>(</span><span>*args, **kwargs)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class ProfileNotFoundError(ConnpyError):
&#34;&#34;&#34;Raised when a profile is not found.&#34;&#34;&#34;
pass</code></pre>
</details>
<div class="desc"><p>Raised when a profile is not found.</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li><a title="connpy.services.exceptions.ConnpyError" href="#connpy.services.exceptions.ConnpyError">ConnpyError</a></li>
<li>builtins.Exception</li>
<li>builtins.BaseException</li>
</ul>
</dd>
<dt id="connpy.services.exceptions.ReservedNameError"><code class="flex name class">
<span>class <span class="ident">ReservedNameError</span></span>
<span>(</span><span>*args, **kwargs)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class ReservedNameError(ConnpyError):
&#34;&#34;&#34;Raised when a node name conflicts with a reserved command.&#34;&#34;&#34;
pass</code></pre>
</details>
<div class="desc"><p>Raised when a node name conflicts with a reserved command.</p></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li><a title="connpy.services.exceptions.ConnpyError" href="#connpy.services.exceptions.ConnpyError">ConnpyError</a></li>
<li>builtins.Exception</li>
<li>builtins.BaseException</li>
</ul>
</dd>
</dl>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="connpy.services" href="index.html">connpy.services</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="connpy.services.exceptions.ConnpyError" href="#connpy.services.exceptions.ConnpyError">ConnpyError</a></code></h4>
</li>
<li>
<h4><code><a title="connpy.services.exceptions.ExecutionError" href="#connpy.services.exceptions.ExecutionError">ExecutionError</a></code></h4>
</li>
<li>
<h4><code><a title="connpy.services.exceptions.InvalidConfigurationError" href="#connpy.services.exceptions.InvalidConfigurationError">InvalidConfigurationError</a></code></h4>
</li>
<li>
<h4><code><a title="connpy.services.exceptions.NodeAlreadyExistsError" href="#connpy.services.exceptions.NodeAlreadyExistsError">NodeAlreadyExistsError</a></code></h4>
</li>
<li>
<h4><code><a title="connpy.services.exceptions.NodeNotFoundError" href="#connpy.services.exceptions.NodeNotFoundError">NodeNotFoundError</a></code></h4>
</li>
<li>
<h4><code><a title="connpy.services.exceptions.ProfileAlreadyExistsError" href="#connpy.services.exceptions.ProfileAlreadyExistsError">ProfileAlreadyExistsError</a></code></h4>
</li>
<li>
<h4><code><a title="connpy.services.exceptions.ProfileNotFoundError" href="#connpy.services.exceptions.ProfileNotFoundError">ProfileNotFoundError</a></code></h4>
</li>
<li>
<h4><code><a title="connpy.services.exceptions.ReservedNameError" href="#connpy.services.exceptions.ReservedNameError">ReservedNameError</a></code></h4>
</li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.5</a>.</p>
</footer>
</body>
</html>
+401
View File
@@ -0,0 +1,401 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.5">
<title>connpy.services.execution_service API documentation</title>
<meta name="description" content="">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source > summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible;min-width:max-content}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin:1em 0}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
/* Collapse source docstrings */
setTimeout(() => {
[...document.querySelectorAll('.hljs.language-python > .hljs-string')]
.filter(el => el.innerHTML.length > 200 && ['"""', "'''"].includes(el.innerHTML.substring(0, 3)))
.forEach(el => {
let d = document.createElement('details');
d.classList.add('hljs-string');
d.innerHTML = '<summary>"""</summary>' + el.innerHTML.substring(3);
el.replaceWith(d);
});
}, 100);
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>connpy.services.execution_service</code></h1>
</header>
<section id="section-intro">
</section>
<section>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="connpy.services.execution_service.ExecutionService"><code class="flex name class">
<span>class <span class="ident">ExecutionService</span></span>
<span>(</span><span>config=None)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class ExecutionService(BaseService):
&#34;&#34;&#34;Business logic for executing commands on nodes and running automation scripts.&#34;&#34;&#34;
def run_commands(
self,
nodes_filter: str,
commands: List[str],
variables: Optional[Dict[str, Any]] = None,
parallel: int = 10,
timeout: int = 10,
folder: Optional[str] = None,
prompt: Optional[str] = None,
on_node_complete: Optional[Callable] = None,
logger: Optional[Callable] = None
) -&gt; Dict[str, str]:
&#34;&#34;&#34;Execute commands on a set of nodes.&#34;&#34;&#34;
try:
matched_names = self.config._getallnodes(nodes_filter)
if not matched_names:
raise ConnpyError(f&#34;No nodes found matching filter: {nodes_filter}&#34;)
node_data = self.config.getitems(matched_names, extract=True)
executor = Nodes(node_data, config=self.config)
self.last_executor = executor
results = executor.run(
commands=commands,
vars=variables,
parallel=parallel,
timeout=timeout,
folder=folder,
prompt=prompt,
on_complete=on_node_complete,
logger=logger
)
return results
except Exception as e:
raise ConnpyError(f&#34;Execution failed: {e}&#34;)
def test_commands(
self,
nodes_filter: str,
commands: List[str],
expected: List[str],
variables: Optional[Dict[str, Any]] = None,
parallel: int = 10,
timeout: int = 10,
prompt: Optional[str] = None,
on_node_complete: Optional[Callable] = None,
logger: Optional[Callable] = None
) -&gt; Dict[str, Dict[str, bool]]:
&#34;&#34;&#34;Run commands and verify expected output on a set of nodes.&#34;&#34;&#34;
try:
matched_names = self.config._getallnodes(nodes_filter)
if not matched_names:
raise ConnpyError(f&#34;No nodes found matching filter: {nodes_filter}&#34;)
node_data = self.config.getitems(matched_names, extract=True)
executor = Nodes(node_data, config=self.config)
self.last_executor = executor
results = executor.test(
commands=commands,
expected=expected,
vars=variables,
parallel=parallel,
timeout=timeout,
prompt=prompt,
on_complete=on_node_complete,
logger=logger
)
return results
except Exception as e:
raise ConnpyError(f&#34;Testing failed: {e}&#34;)
def run_cli_script(self, nodes_filter: str, script_path: str, parallel: int = 10) -&gt; Dict[str, str]:
&#34;&#34;&#34;Run a plain-text script containing one command per line.&#34;&#34;&#34;
if not os.path.exists(script_path):
raise ConnpyError(f&#34;Script file not found: {script_path}&#34;)
try:
with open(script_path, &#34;r&#34;) as f:
commands = [line.strip() for line in f if line.strip()]
except Exception as e:
raise ConnpyError(f&#34;Failed to read script {script_path}: {e}&#34;)
return self.run_commands(nodes_filter, commands, parallel=parallel)
def run_yaml_playbook(self, playbook_path: str, parallel: int = 10) -&gt; Dict[str, Any]:
&#34;&#34;&#34;Run a structured Connpy YAML automation playbook.&#34;&#34;&#34;
if not os.path.exists(playbook_path):
raise ConnpyError(f&#34;Playbook file not found: {playbook_path}&#34;)
try:
with open(playbook_path, &#34;r&#34;) as f:
playbook = yaml.load(f, Loader=yaml.FullLoader)
except Exception as e:
raise ConnpyError(f&#34;Failed to load playbook {playbook_path}: {e}&#34;)
# Basic validation
if not isinstance(playbook, dict) or &#34;nodes&#34; not in playbook or &#34;commands&#34; not in playbook:
raise ConnpyError(&#34;Invalid playbook format: missing &#39;nodes&#39; or &#39;commands&#39; keys.&#34;)
action = playbook.get(&#34;action&#34;, &#34;run&#34;)
if action == &#34;run&#34;:
return self.run_commands(
nodes_filter=playbook[&#34;nodes&#34;],
commands=playbook[&#34;commands&#34;],
parallel=parallel,
timeout=playbook.get(&#34;timeout&#34;, 10)
)
elif action == &#34;test&#34;:
return self.test_commands(
nodes_filter=playbook[&#34;nodes&#34;],
commands=playbook[&#34;commands&#34;],
expected=playbook.get(&#34;expected&#34;, []),
parallel=parallel,
timeout=playbook.get(&#34;timeout&#34;, 10)
)
else:
raise ConnpyError(f&#34;Unsupported playbook action: {action}&#34;)</code></pre>
</details>
<div class="desc"><p>Business logic for executing commands on nodes and running automation scripts.</p>
<p>Initialize the service.</p>
<h2 id="args">Args</h2>
<dl>
<dt><strong><code>config</code></strong></dt>
<dd>An instance of configfile (or None to instantiate a new one/use global context).</dd>
</dl></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li><a title="connpy.services.base.BaseService" href="base.html#connpy.services.base.BaseService">BaseService</a></li>
</ul>
<h3>Methods</h3>
<dl>
<dt id="connpy.services.execution_service.ExecutionService.run_cli_script"><code class="name flex">
<span>def <span class="ident">run_cli_script</span></span>(<span>self, nodes_filter: str, script_path: str, parallel: int = 10) > Dict[str, str]</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def run_cli_script(self, nodes_filter: str, script_path: str, parallel: int = 10) -&gt; Dict[str, str]:
&#34;&#34;&#34;Run a plain-text script containing one command per line.&#34;&#34;&#34;
if not os.path.exists(script_path):
raise ConnpyError(f&#34;Script file not found: {script_path}&#34;)
try:
with open(script_path, &#34;r&#34;) as f:
commands = [line.strip() for line in f if line.strip()]
except Exception as e:
raise ConnpyError(f&#34;Failed to read script {script_path}: {e}&#34;)
return self.run_commands(nodes_filter, commands, parallel=parallel)</code></pre>
</details>
<div class="desc"><p>Run a plain-text script containing one command per line.</p></div>
</dd>
<dt id="connpy.services.execution_service.ExecutionService.run_commands"><code class="name flex">
<span>def <span class="ident">run_commands</span></span>(<span>self,<br>nodes_filter: str,<br>commands: List[str],<br>variables: Dict[str, Any] | None = None,<br>parallel: int = 10,<br>timeout: int = 10,<br>folder: str | None = None,<br>prompt: str | None = None,<br>on_node_complete: Callable | None = None,<br>logger: Callable | None = None) > Dict[str, str]</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def run_commands(
self,
nodes_filter: str,
commands: List[str],
variables: Optional[Dict[str, Any]] = None,
parallel: int = 10,
timeout: int = 10,
folder: Optional[str] = None,
prompt: Optional[str] = None,
on_node_complete: Optional[Callable] = None,
logger: Optional[Callable] = None
) -&gt; Dict[str, str]:
&#34;&#34;&#34;Execute commands on a set of nodes.&#34;&#34;&#34;
try:
matched_names = self.config._getallnodes(nodes_filter)
if not matched_names:
raise ConnpyError(f&#34;No nodes found matching filter: {nodes_filter}&#34;)
node_data = self.config.getitems(matched_names, extract=True)
executor = Nodes(node_data, config=self.config)
self.last_executor = executor
results = executor.run(
commands=commands,
vars=variables,
parallel=parallel,
timeout=timeout,
folder=folder,
prompt=prompt,
on_complete=on_node_complete,
logger=logger
)
return results
except Exception as e:
raise ConnpyError(f&#34;Execution failed: {e}&#34;)</code></pre>
</details>
<div class="desc"><p>Execute commands on a set of nodes.</p></div>
</dd>
<dt id="connpy.services.execution_service.ExecutionService.run_yaml_playbook"><code class="name flex">
<span>def <span class="ident">run_yaml_playbook</span></span>(<span>self, playbook_path: str, parallel: int = 10) > Dict[str, Any]</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def run_yaml_playbook(self, playbook_path: str, parallel: int = 10) -&gt; Dict[str, Any]:
&#34;&#34;&#34;Run a structured Connpy YAML automation playbook.&#34;&#34;&#34;
if not os.path.exists(playbook_path):
raise ConnpyError(f&#34;Playbook file not found: {playbook_path}&#34;)
try:
with open(playbook_path, &#34;r&#34;) as f:
playbook = yaml.load(f, Loader=yaml.FullLoader)
except Exception as e:
raise ConnpyError(f&#34;Failed to load playbook {playbook_path}: {e}&#34;)
# Basic validation
if not isinstance(playbook, dict) or &#34;nodes&#34; not in playbook or &#34;commands&#34; not in playbook:
raise ConnpyError(&#34;Invalid playbook format: missing &#39;nodes&#39; or &#39;commands&#39; keys.&#34;)
action = playbook.get(&#34;action&#34;, &#34;run&#34;)
if action == &#34;run&#34;:
return self.run_commands(
nodes_filter=playbook[&#34;nodes&#34;],
commands=playbook[&#34;commands&#34;],
parallel=parallel,
timeout=playbook.get(&#34;timeout&#34;, 10)
)
elif action == &#34;test&#34;:
return self.test_commands(
nodes_filter=playbook[&#34;nodes&#34;],
commands=playbook[&#34;commands&#34;],
expected=playbook.get(&#34;expected&#34;, []),
parallel=parallel,
timeout=playbook.get(&#34;timeout&#34;, 10)
)
else:
raise ConnpyError(f&#34;Unsupported playbook action: {action}&#34;)</code></pre>
</details>
<div class="desc"><p>Run a structured Connpy YAML automation playbook.</p></div>
</dd>
<dt id="connpy.services.execution_service.ExecutionService.test_commands"><code class="name flex">
<span>def <span class="ident">test_commands</span></span>(<span>self,<br>nodes_filter: str,<br>commands: List[str],<br>expected: List[str],<br>variables: Dict[str, Any] | None = None,<br>parallel: int = 10,<br>timeout: int = 10,<br>prompt: str | None = None,<br>on_node_complete: Callable | None = None,<br>logger: Callable | None = None) > Dict[str, Dict[str, bool]]</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def test_commands(
self,
nodes_filter: str,
commands: List[str],
expected: List[str],
variables: Optional[Dict[str, Any]] = None,
parallel: int = 10,
timeout: int = 10,
prompt: Optional[str] = None,
on_node_complete: Optional[Callable] = None,
logger: Optional[Callable] = None
) -&gt; Dict[str, Dict[str, bool]]:
&#34;&#34;&#34;Run commands and verify expected output on a set of nodes.&#34;&#34;&#34;
try:
matched_names = self.config._getallnodes(nodes_filter)
if not matched_names:
raise ConnpyError(f&#34;No nodes found matching filter: {nodes_filter}&#34;)
node_data = self.config.getitems(matched_names, extract=True)
executor = Nodes(node_data, config=self.config)
self.last_executor = executor
results = executor.test(
commands=commands,
expected=expected,
vars=variables,
parallel=parallel,
timeout=timeout,
prompt=prompt,
on_complete=on_node_complete,
logger=logger
)
return results
except Exception as e:
raise ConnpyError(f&#34;Testing failed: {e}&#34;)</code></pre>
</details>
<div class="desc"><p>Run commands and verify expected output on a set of nodes.</p></div>
</dd>
</dl>
<h3>Inherited members</h3>
<ul class="hlist">
<li><code><b><a title="connpy.services.base.BaseService" href="base.html#connpy.services.base.BaseService">BaseService</a></b></code>:
<ul class="hlist">
<li><code><a title="connpy.services.base.BaseService.set_reserved_names" href="base.html#connpy.services.base.BaseService.set_reserved_names">set_reserved_names</a></code></li>
</ul>
</li>
</ul>
</dd>
</dl>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="connpy.services" href="index.html">connpy.services</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="connpy.services.execution_service.ExecutionService" href="#connpy.services.execution_service.ExecutionService">ExecutionService</a></code></h4>
<ul class="">
<li><code><a title="connpy.services.execution_service.ExecutionService.run_cli_script" href="#connpy.services.execution_service.ExecutionService.run_cli_script">run_cli_script</a></code></li>
<li><code><a title="connpy.services.execution_service.ExecutionService.run_commands" href="#connpy.services.execution_service.ExecutionService.run_commands">run_commands</a></code></li>
<li><code><a title="connpy.services.execution_service.ExecutionService.run_yaml_playbook" href="#connpy.services.execution_service.ExecutionService.run_yaml_playbook">run_yaml_playbook</a></code></li>
<li><code><a title="connpy.services.execution_service.ExecutionService.test_commands" href="#connpy.services.execution_service.ExecutionService.test_commands">test_commands</a></code></li>
</ul>
</li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.5</a>.</p>
</footer>
</body>
</html>
@@ -0,0 +1,285 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.5">
<title>connpy.services.import_export_service API documentation</title>
<meta name="description" content="">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source > summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible;min-width:max-content}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin:1em 0}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
/* Collapse source docstrings */
setTimeout(() => {
[...document.querySelectorAll('.hljs.language-python > .hljs-string')]
.filter(el => el.innerHTML.length > 200 && ['"""', "'''"].includes(el.innerHTML.substring(0, 3)))
.forEach(el => {
let d = document.createElement('details');
d.classList.add('hljs-string');
d.innerHTML = '<summary>"""</summary>' + el.innerHTML.substring(3);
el.replaceWith(d);
});
}, 100);
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>connpy.services.import_export_service</code></h1>
</header>
<section id="section-intro">
</section>
<section>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="connpy.services.import_export_service.ImportExportService"><code class="flex name class">
<span>class <span class="ident">ImportExportService</span></span>
<span>(</span><span>config=None)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class ImportExportService(BaseService):
&#34;&#34;&#34;Business logic for YAML/JSON inventory import and export.&#34;&#34;&#34;
def export_to_file(self, file_path, folders=None):
&#34;&#34;&#34;Export nodes/folders to a YAML file.&#34;&#34;&#34;
if os.path.exists(file_path):
raise InvalidConfigurationError(f&#34;File &#39;{file_path}&#39; already exists.&#34;)
data = self.export_to_dict(folders)
try:
with open(file_path, &#34;w&#34;) as f:
yaml.dump(data, f, Dumper=NoAliasDumper, default_flow_style=False)
except OSError as e:
raise InvalidConfigurationError(f&#34;Failed to export to &#39;{file_path}&#39;: {e}&#34;)
def export_to_dict(self, folders=None):
&#34;&#34;&#34;Export nodes/folders to a dictionary.&#34;&#34;&#34;
if not folders:
return self.config._getallnodesfull(extract=False)
else:
# Validate folders exist
for f in folders:
if f != &#34;@&#34; and f not in self.config._getallfolders():
raise NodeNotFoundError(f&#34;Folder &#39;{f}&#39; not found.&#34;)
return self.config._getallnodesfull(folders, extract=False)
def import_from_file(self, file_path):
&#34;&#34;&#34;Import nodes/folders from a YAML file.&#34;&#34;&#34;
if not os.path.exists(file_path):
raise InvalidConfigurationError(f&#34;File &#39;{file_path}&#39; does not exist.&#34;)
try:
with open(file_path, &#34;r&#34;) as f:
data = yaml.load(f, Loader=yaml.FullLoader)
self.import_from_dict(data)
except Exception as e:
raise InvalidConfigurationError(f&#34;Failed to read/parse import file: {e}&#34;)
def import_from_dict(self, data):
&#34;&#34;&#34;Import nodes/folders from a dictionary.&#34;&#34;&#34;
if not isinstance(data, dict):
raise InvalidConfigurationError(&#34;Invalid import data format: expected a dictionary of nodes.&#34;)
# Process imports
for k, v in data.items():
uniques = self.config._explode_unique(k)
# Ensure folders exist
if &#34;folder&#34; in uniques:
folder_name = f&#34;@{uniques[&#39;folder&#39;]}&#34;
if folder_name not in self.config._getallfolders():
folder_uniques = self.config._explode_unique(folder_name)
self.config._folder_add(**folder_uniques)
if &#34;subfolder&#34; in uniques:
sub_name = f&#34;@{uniques[&#39;subfolder&#39;]}@{uniques[&#39;folder&#39;]}&#34;
if sub_name not in self.config._getallfolders():
sub_uniques = self.config._explode_unique(sub_name)
self.config._folder_add(**sub_uniques)
# Add node/connection
v.update(uniques)
self._validate_node_name(k)
self.config._connections_add(**v)
self.config._saveconfig(self.config.file)</code></pre>
</details>
<div class="desc"><p>Business logic for YAML/JSON inventory import and export.</p>
<p>Initialize the service.</p>
<h2 id="args">Args</h2>
<dl>
<dt><strong><code>config</code></strong></dt>
<dd>An instance of configfile (or None to instantiate a new one/use global context).</dd>
</dl></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li><a title="connpy.services.base.BaseService" href="base.html#connpy.services.base.BaseService">BaseService</a></li>
</ul>
<h3>Methods</h3>
<dl>
<dt id="connpy.services.import_export_service.ImportExportService.export_to_dict"><code class="name flex">
<span>def <span class="ident">export_to_dict</span></span>(<span>self, folders=None)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def export_to_dict(self, folders=None):
&#34;&#34;&#34;Export nodes/folders to a dictionary.&#34;&#34;&#34;
if not folders:
return self.config._getallnodesfull(extract=False)
else:
# Validate folders exist
for f in folders:
if f != &#34;@&#34; and f not in self.config._getallfolders():
raise NodeNotFoundError(f&#34;Folder &#39;{f}&#39; not found.&#34;)
return self.config._getallnodesfull(folders, extract=False)</code></pre>
</details>
<div class="desc"><p>Export nodes/folders to a dictionary.</p></div>
</dd>
<dt id="connpy.services.import_export_service.ImportExportService.export_to_file"><code class="name flex">
<span>def <span class="ident">export_to_file</span></span>(<span>self, file_path, folders=None)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def export_to_file(self, file_path, folders=None):
&#34;&#34;&#34;Export nodes/folders to a YAML file.&#34;&#34;&#34;
if os.path.exists(file_path):
raise InvalidConfigurationError(f&#34;File &#39;{file_path}&#39; already exists.&#34;)
data = self.export_to_dict(folders)
try:
with open(file_path, &#34;w&#34;) as f:
yaml.dump(data, f, Dumper=NoAliasDumper, default_flow_style=False)
except OSError as e:
raise InvalidConfigurationError(f&#34;Failed to export to &#39;{file_path}&#39;: {e}&#34;)</code></pre>
</details>
<div class="desc"><p>Export nodes/folders to a YAML file.</p></div>
</dd>
<dt id="connpy.services.import_export_service.ImportExportService.import_from_dict"><code class="name flex">
<span>def <span class="ident">import_from_dict</span></span>(<span>self, data)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def import_from_dict(self, data):
&#34;&#34;&#34;Import nodes/folders from a dictionary.&#34;&#34;&#34;
if not isinstance(data, dict):
raise InvalidConfigurationError(&#34;Invalid import data format: expected a dictionary of nodes.&#34;)
# Process imports
for k, v in data.items():
uniques = self.config._explode_unique(k)
# Ensure folders exist
if &#34;folder&#34; in uniques:
folder_name = f&#34;@{uniques[&#39;folder&#39;]}&#34;
if folder_name not in self.config._getallfolders():
folder_uniques = self.config._explode_unique(folder_name)
self.config._folder_add(**folder_uniques)
if &#34;subfolder&#34; in uniques:
sub_name = f&#34;@{uniques[&#39;subfolder&#39;]}@{uniques[&#39;folder&#39;]}&#34;
if sub_name not in self.config._getallfolders():
sub_uniques = self.config._explode_unique(sub_name)
self.config._folder_add(**sub_uniques)
# Add node/connection
v.update(uniques)
self._validate_node_name(k)
self.config._connections_add(**v)
self.config._saveconfig(self.config.file)</code></pre>
</details>
<div class="desc"><p>Import nodes/folders from a dictionary.</p></div>
</dd>
<dt id="connpy.services.import_export_service.ImportExportService.import_from_file"><code class="name flex">
<span>def <span class="ident">import_from_file</span></span>(<span>self, file_path)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def import_from_file(self, file_path):
&#34;&#34;&#34;Import nodes/folders from a YAML file.&#34;&#34;&#34;
if not os.path.exists(file_path):
raise InvalidConfigurationError(f&#34;File &#39;{file_path}&#39; does not exist.&#34;)
try:
with open(file_path, &#34;r&#34;) as f:
data = yaml.load(f, Loader=yaml.FullLoader)
self.import_from_dict(data)
except Exception as e:
raise InvalidConfigurationError(f&#34;Failed to read/parse import file: {e}&#34;)</code></pre>
</details>
<div class="desc"><p>Import nodes/folders from a YAML file.</p></div>
</dd>
</dl>
<h3>Inherited members</h3>
<ul class="hlist">
<li><code><b><a title="connpy.services.base.BaseService" href="base.html#connpy.services.base.BaseService">BaseService</a></b></code>:
<ul class="hlist">
<li><code><a title="connpy.services.base.BaseService.set_reserved_names" href="base.html#connpy.services.base.BaseService.set_reserved_names">set_reserved_names</a></code></li>
</ul>
</li>
</ul>
</dd>
</dl>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="connpy.services" href="index.html">connpy.services</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="connpy.services.import_export_service.ImportExportService" href="#connpy.services.import_export_service.ImportExportService">ImportExportService</a></code></h4>
<ul class="">
<li><code><a title="connpy.services.import_export_service.ImportExportService.export_to_dict" href="#connpy.services.import_export_service.ImportExportService.export_to_dict">export_to_dict</a></code></li>
<li><code><a title="connpy.services.import_export_service.ImportExportService.export_to_file" href="#connpy.services.import_export_service.ImportExportService.export_to_file">export_to_file</a></code></li>
<li><code><a title="connpy.services.import_export_service.ImportExportService.import_from_dict" href="#connpy.services.import_export_service.ImportExportService.import_from_dict">import_from_dict</a></code></li>
<li><code><a title="connpy.services.import_export_service.ImportExportService.import_from_file" href="#connpy.services.import_export_service.ImportExportService.import_from_file">import_from_file</a></code></li>
</ul>
</li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.5</a>.</p>
</footer>
</body>
</html>
File diff suppressed because it is too large Load Diff
+745
View File
@@ -0,0 +1,745 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.5">
<title>connpy.services.node_service API documentation</title>
<meta name="description" content="">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source > summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible;min-width:max-content}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin:1em 0}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
/* Collapse source docstrings */
setTimeout(() => {
[...document.querySelectorAll('.hljs.language-python > .hljs-string')]
.filter(el => el.innerHTML.length > 200 && ['"""', "'''"].includes(el.innerHTML.substring(0, 3)))
.forEach(el => {
let d = document.createElement('details');
d.classList.add('hljs-string');
d.innerHTML = '<summary>"""</summary>' + el.innerHTML.substring(3);
el.replaceWith(d);
});
}, 100);
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>connpy.services.node_service</code></h1>
</header>
<section id="section-intro">
</section>
<section>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="connpy.services.node_service.NodeService"><code class="flex name class">
<span>class <span class="ident">NodeService</span></span>
<span>(</span><span>config=None)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class NodeService(BaseService):
def __init__(self, config=None):
super().__init__(config)
def list_nodes(self, filter_str=None, format_str=None):
&#34;&#34;&#34;Return a listed filtered by regex match and formatted if needed.&#34;&#34;&#34;
nodes = self.config._getallnodes()
case_sensitive = self.config.config.get(&#34;case&#34;, False)
if filter_str:
flags = re.IGNORECASE if not case_sensitive else 0
nodes = [n for n in nodes if re.search(filter_str, n, flags)]
if not format_str:
return nodes
from .profile_service import ProfileService
profile_service = ProfileService(self.config)
formatted_nodes = []
for n_id in nodes:
# Use ProfileService to resolve profiles for dynamic formatting
details = self.config.getitem(n_id, extract=False)
if details:
details = profile_service.resolve_node_data(details)
name = n_id.split(&#34;@&#34;)[0]
location = n_id.partition(&#34;@&#34;)[2] or &#34;root&#34;
# Prepare context for .format() with all details
context = details.copy()
context.update({
&#34;name&#34;: name,
&#34;NAME&#34;: name.upper(),
&#34;location&#34;: location,
&#34;LOCATION&#34;: location.upper(),
})
# Add exploded uniques (id, folder, subfolder)
uniques = self.config._explode_unique(n_id)
if uniques:
context.update(uniques)
# Add uppercase versions of all keys for convenience
for k, v in list(context.items()):
if isinstance(v, str):
context[k.upper()] = v.upper()
try:
formatted_nodes.append(format_str.format(**context))
except (KeyError, IndexError, ValueError):
# Fallback to original string if format fails
formatted_nodes.append(n_id)
return formatted_nodes
def list_folders(self, filter_str=None):
&#34;&#34;&#34;Return all unique folders, optionally filtered by regex.&#34;&#34;&#34;
folders = self.config._getallfolders()
case_sensitive = self.config.config.get(&#34;case&#34;, False)
if filter_str:
flags = re.IGNORECASE if not case_sensitive else 0
folders = [f for f in folders if re.search(filter_str, f, flags)]
return folders
def get_node_details(self, unique_id):
&#34;&#34;&#34;Return full configuration dictionary for a specific node.&#34;&#34;&#34;
details = self.config.getitem(unique_id)
if not details:
raise NodeNotFoundError(f&#34;Node &#39;{unique_id}&#39; not found.&#34;)
return details
def explode_unique(self, unique_id):
&#34;&#34;&#34;Explode a unique ID into a dictionary of its parts.&#34;&#34;&#34;
return self.config._explode_unique(unique_id)
def generate_cache(self, nodes=None, folders=None, profiles=None):
&#34;&#34;&#34;Generate and update the internal nodes cache.&#34;&#34;&#34;
self.config._generate_nodes_cache(nodes=nodes, folders=folders, profiles=profiles)
def add_node(self, unique_id, data, is_folder=False):
&#34;&#34;&#34;Logic for adding a new node or folder to configuration.&#34;&#34;&#34;
if not is_folder:
self._validate_node_name(unique_id)
all_nodes = self.config._getallnodes()
all_folders = self.config._getallfolders()
if is_folder:
if unique_id in all_folders:
raise NodeAlreadyExistsError(f&#34;Folder &#39;{unique_id}&#39; already exists.&#34;)
uniques = self.config._explode_unique(unique_id)
if not uniques:
raise InvalidConfigurationError(f&#34;Invalid folder name &#39;{unique_id}&#39;.&#34;)
# Check if parent folder exists when creating a subfolder
if &#34;subfolder&#34; in uniques:
parent_folder = f&#34;@{uniques[&#39;folder&#39;]}&#34;
if parent_folder not in all_folders:
raise NodeNotFoundError(f&#34;Folder &#39;{parent_folder}&#39; not found.&#34;)
self.config._folder_add(**uniques)
self.config._saveconfig(self.config.file)
else:
if unique_id in all_nodes:
raise NodeAlreadyExistsError(f&#34;Node &#39;{unique_id}&#39; already exists.&#34;)
# Check if parent folder exists when creating a node in a folder
node_folder = unique_id.partition(&#34;@&#34;)[2]
if node_folder:
parent_folder = f&#34;@{node_folder}&#34;
if parent_folder not in all_folders:
raise NodeNotFoundError(f&#34;Folder &#39;{parent_folder}&#39; not found.&#34;)
# Ensure &#39;id&#39; is in data for config._connections_add
if &#34;id&#34; not in data:
uniques = self.config._explode_unique(unique_id)
if uniques and &#34;id&#34; in uniques:
data[&#34;id&#34;] = uniques[&#34;id&#34;]
self.config._connections_add(**data)
self.config._saveconfig(self.config.file)
def update_node(self, unique_id, data):
&#34;&#34;&#34;Explicitly update an existing node.&#34;&#34;&#34;
all_nodes = self.config._getallnodes()
if unique_id not in all_nodes:
raise NodeNotFoundError(f&#34;Node &#39;{unique_id}&#39; not found.&#34;)
# Ensure &#39;id&#39; is in data for config._connections_add
if &#34;id&#34; not in data:
uniques = self.config._explode_unique(unique_id)
if uniques:
data[&#34;id&#34;] = uniques[&#34;id&#34;]
# config._connections_add actually handles updates if ID exists correctly
self.config._connections_add(**data)
self.config._saveconfig(self.config.file)
def delete_node(self, unique_id, is_folder=False):
&#34;&#34;&#34;Logic for deleting a node or folder.&#34;&#34;&#34;
if is_folder:
uniques = self.config._explode_unique(unique_id)
if not uniques:
raise NodeNotFoundError(f&#34;Folder &#39;{unique_id}&#39; not found or invalid.&#34;)
self.config._folder_del(**uniques)
else:
uniques = self.config._explode_unique(unique_id)
if not uniques:
raise NodeNotFoundError(f&#34;Node &#39;{unique_id}&#39; not found or invalid.&#34;)
self.config._connections_del(**uniques)
self.config._saveconfig(self.config.file)
def connect_node(self, unique_id, sftp=False, debug=False, logger=None):
&#34;&#34;&#34;Interact with a node directly.&#34;&#34;&#34;
from connpy.core import node
from .profile_service import ProfileService
node_data = self.config.getitem(unique_id, extract=False)
if not node_data:
raise NodeNotFoundError(f&#34;Node &#39;{unique_id}&#39; not found.&#34;)
# Resolve profiles
profile_service = ProfileService(self.config)
resolved_data = profile_service.resolve_node_data(node_data)
n = node(unique_id, **resolved_data, config=self.config)
if sftp:
n.protocol = &#34;sftp&#34;
n.interact(debug=debug, logger=logger)
def move_node(self, src_id, dst_id, copy=False):
&#34;&#34;&#34;Move or copy a node.&#34;&#34;&#34;
self._validate_node_name(dst_id)
node_data = self.config.getitem(src_id)
if not node_data:
raise NodeNotFoundError(f&#34;Source node &#39;{src_id}&#39; not found.&#34;)
if dst_id in self.config._getallnodes():
raise NodeAlreadyExistsError(f&#34;Destination node &#39;{dst_id}&#39; already exists.&#34;)
new_uniques = self.config._explode_unique(dst_id)
if not new_uniques:
raise InvalidConfigurationError(f&#34;Invalid destination format &#39;{dst_id}&#39;.&#34;)
new_node_data = node_data.copy()
new_node_data.update(new_uniques)
self.config._connections_add(**new_node_data)
if not copy:
src_uniques = self.config._explode_unique(src_id)
self.config._connections_del(**src_uniques)
self.config._saveconfig(self.config.file)
def bulk_add(self, ids, hosts, common_data):
&#34;&#34;&#34;Add multiple nodes with shared common configuration.&#34;&#34;&#34;
count = 0
all_nodes = self.config._getallnodes()
for i, uid in enumerate(ids):
if uid in all_nodes:
continue
try:
self._validate_node_name(uid)
except ReservedNameError:
# For bulk, we might want to just skip or log.
# CLI caller will handle if it wants to be strict.
continue
host = hosts[i] if i &lt; len(hosts) else hosts[0]
uniques = self.config._explode_unique(uid)
if not uniques:
continue
node_data = common_data.copy()
node_data.pop(&#34;ids&#34;, None)
node_data.pop(&#34;location&#34;, None)
node_data.update(uniques)
node_data[&#34;host&#34;] = host
node_data[&#34;type&#34;] = &#34;connection&#34;
self.config._connections_add(**node_data)
count += 1
if count &gt; 0:
self.config._saveconfig(self.config.file)
return count
def full_replace(self, connections, profiles):
&#34;&#34;&#34;Replace all connections and profiles with new data.&#34;&#34;&#34;
self.config.connections = connections
self.config.profiles = profiles
self.config._saveconfig(self.config.file)
def get_inventory(self):
&#34;&#34;&#34;Return a full snapshot of connections and profiles.&#34;&#34;&#34;
return {
&#34;connections&#34;: self.config.connections,
&#34;profiles&#34;: self.config.profiles
}</code></pre>
</details>
<div class="desc"><p>Base class for all connpy services, providing common configuration access.</p>
<p>Initialize the service.</p>
<h2 id="args">Args</h2>
<dl>
<dt><strong><code>config</code></strong></dt>
<dd>An instance of configfile (or None to instantiate a new one/use global context).</dd>
</dl></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li><a title="connpy.services.base.BaseService" href="base.html#connpy.services.base.BaseService">BaseService</a></li>
</ul>
<h3>Methods</h3>
<dl>
<dt id="connpy.services.node_service.NodeService.add_node"><code class="name flex">
<span>def <span class="ident">add_node</span></span>(<span>self, unique_id, data, is_folder=False)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def add_node(self, unique_id, data, is_folder=False):
&#34;&#34;&#34;Logic for adding a new node or folder to configuration.&#34;&#34;&#34;
if not is_folder:
self._validate_node_name(unique_id)
all_nodes = self.config._getallnodes()
all_folders = self.config._getallfolders()
if is_folder:
if unique_id in all_folders:
raise NodeAlreadyExistsError(f&#34;Folder &#39;{unique_id}&#39; already exists.&#34;)
uniques = self.config._explode_unique(unique_id)
if not uniques:
raise InvalidConfigurationError(f&#34;Invalid folder name &#39;{unique_id}&#39;.&#34;)
# Check if parent folder exists when creating a subfolder
if &#34;subfolder&#34; in uniques:
parent_folder = f&#34;@{uniques[&#39;folder&#39;]}&#34;
if parent_folder not in all_folders:
raise NodeNotFoundError(f&#34;Folder &#39;{parent_folder}&#39; not found.&#34;)
self.config._folder_add(**uniques)
self.config._saveconfig(self.config.file)
else:
if unique_id in all_nodes:
raise NodeAlreadyExistsError(f&#34;Node &#39;{unique_id}&#39; already exists.&#34;)
# Check if parent folder exists when creating a node in a folder
node_folder = unique_id.partition(&#34;@&#34;)[2]
if node_folder:
parent_folder = f&#34;@{node_folder}&#34;
if parent_folder not in all_folders:
raise NodeNotFoundError(f&#34;Folder &#39;{parent_folder}&#39; not found.&#34;)
# Ensure &#39;id&#39; is in data for config._connections_add
if &#34;id&#34; not in data:
uniques = self.config._explode_unique(unique_id)
if uniques and &#34;id&#34; in uniques:
data[&#34;id&#34;] = uniques[&#34;id&#34;]
self.config._connections_add(**data)
self.config._saveconfig(self.config.file)</code></pre>
</details>
<div class="desc"><p>Logic for adding a new node or folder to configuration.</p></div>
</dd>
<dt id="connpy.services.node_service.NodeService.bulk_add"><code class="name flex">
<span>def <span class="ident">bulk_add</span></span>(<span>self, ids, hosts, common_data)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def bulk_add(self, ids, hosts, common_data):
&#34;&#34;&#34;Add multiple nodes with shared common configuration.&#34;&#34;&#34;
count = 0
all_nodes = self.config._getallnodes()
for i, uid in enumerate(ids):
if uid in all_nodes:
continue
try:
self._validate_node_name(uid)
except ReservedNameError:
# For bulk, we might want to just skip or log.
# CLI caller will handle if it wants to be strict.
continue
host = hosts[i] if i &lt; len(hosts) else hosts[0]
uniques = self.config._explode_unique(uid)
if not uniques:
continue
node_data = common_data.copy()
node_data.pop(&#34;ids&#34;, None)
node_data.pop(&#34;location&#34;, None)
node_data.update(uniques)
node_data[&#34;host&#34;] = host
node_data[&#34;type&#34;] = &#34;connection&#34;
self.config._connections_add(**node_data)
count += 1
if count &gt; 0:
self.config._saveconfig(self.config.file)
return count</code></pre>
</details>
<div class="desc"><p>Add multiple nodes with shared common configuration.</p></div>
</dd>
<dt id="connpy.services.node_service.NodeService.connect_node"><code class="name flex">
<span>def <span class="ident">connect_node</span></span>(<span>self, unique_id, sftp=False, debug=False, logger=None)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def connect_node(self, unique_id, sftp=False, debug=False, logger=None):
&#34;&#34;&#34;Interact with a node directly.&#34;&#34;&#34;
from connpy.core import node
from .profile_service import ProfileService
node_data = self.config.getitem(unique_id, extract=False)
if not node_data:
raise NodeNotFoundError(f&#34;Node &#39;{unique_id}&#39; not found.&#34;)
# Resolve profiles
profile_service = ProfileService(self.config)
resolved_data = profile_service.resolve_node_data(node_data)
n = node(unique_id, **resolved_data, config=self.config)
if sftp:
n.protocol = &#34;sftp&#34;
n.interact(debug=debug, logger=logger)</code></pre>
</details>
<div class="desc"><p>Interact with a node directly.</p></div>
</dd>
<dt id="connpy.services.node_service.NodeService.delete_node"><code class="name flex">
<span>def <span class="ident">delete_node</span></span>(<span>self, unique_id, is_folder=False)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def delete_node(self, unique_id, is_folder=False):
&#34;&#34;&#34;Logic for deleting a node or folder.&#34;&#34;&#34;
if is_folder:
uniques = self.config._explode_unique(unique_id)
if not uniques:
raise NodeNotFoundError(f&#34;Folder &#39;{unique_id}&#39; not found or invalid.&#34;)
self.config._folder_del(**uniques)
else:
uniques = self.config._explode_unique(unique_id)
if not uniques:
raise NodeNotFoundError(f&#34;Node &#39;{unique_id}&#39; not found or invalid.&#34;)
self.config._connections_del(**uniques)
self.config._saveconfig(self.config.file)</code></pre>
</details>
<div class="desc"><p>Logic for deleting a node or folder.</p></div>
</dd>
<dt id="connpy.services.node_service.NodeService.explode_unique"><code class="name flex">
<span>def <span class="ident">explode_unique</span></span>(<span>self, unique_id)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def explode_unique(self, unique_id):
&#34;&#34;&#34;Explode a unique ID into a dictionary of its parts.&#34;&#34;&#34;
return self.config._explode_unique(unique_id)</code></pre>
</details>
<div class="desc"><p>Explode a unique ID into a dictionary of its parts.</p></div>
</dd>
<dt id="connpy.services.node_service.NodeService.full_replace"><code class="name flex">
<span>def <span class="ident">full_replace</span></span>(<span>self, connections, profiles)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def full_replace(self, connections, profiles):
&#34;&#34;&#34;Replace all connections and profiles with new data.&#34;&#34;&#34;
self.config.connections = connections
self.config.profiles = profiles
self.config._saveconfig(self.config.file)</code></pre>
</details>
<div class="desc"><p>Replace all connections and profiles with new data.</p></div>
</dd>
<dt id="connpy.services.node_service.NodeService.generate_cache"><code class="name flex">
<span>def <span class="ident">generate_cache</span></span>(<span>self, nodes=None, folders=None, profiles=None)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def generate_cache(self, nodes=None, folders=None, profiles=None):
&#34;&#34;&#34;Generate and update the internal nodes cache.&#34;&#34;&#34;
self.config._generate_nodes_cache(nodes=nodes, folders=folders, profiles=profiles)</code></pre>
</details>
<div class="desc"><p>Generate and update the internal nodes cache.</p></div>
</dd>
<dt id="connpy.services.node_service.NodeService.get_inventory"><code class="name flex">
<span>def <span class="ident">get_inventory</span></span>(<span>self)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def get_inventory(self):
&#34;&#34;&#34;Return a full snapshot of connections and profiles.&#34;&#34;&#34;
return {
&#34;connections&#34;: self.config.connections,
&#34;profiles&#34;: self.config.profiles
}</code></pre>
</details>
<div class="desc"><p>Return a full snapshot of connections and profiles.</p></div>
</dd>
<dt id="connpy.services.node_service.NodeService.get_node_details"><code class="name flex">
<span>def <span class="ident">get_node_details</span></span>(<span>self, unique_id)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def get_node_details(self, unique_id):
&#34;&#34;&#34;Return full configuration dictionary for a specific node.&#34;&#34;&#34;
details = self.config.getitem(unique_id)
if not details:
raise NodeNotFoundError(f&#34;Node &#39;{unique_id}&#39; not found.&#34;)
return details</code></pre>
</details>
<div class="desc"><p>Return full configuration dictionary for a specific node.</p></div>
</dd>
<dt id="connpy.services.node_service.NodeService.list_folders"><code class="name flex">
<span>def <span class="ident">list_folders</span></span>(<span>self, filter_str=None)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def list_folders(self, filter_str=None):
&#34;&#34;&#34;Return all unique folders, optionally filtered by regex.&#34;&#34;&#34;
folders = self.config._getallfolders()
case_sensitive = self.config.config.get(&#34;case&#34;, False)
if filter_str:
flags = re.IGNORECASE if not case_sensitive else 0
folders = [f for f in folders if re.search(filter_str, f, flags)]
return folders</code></pre>
</details>
<div class="desc"><p>Return all unique folders, optionally filtered by regex.</p></div>
</dd>
<dt id="connpy.services.node_service.NodeService.list_nodes"><code class="name flex">
<span>def <span class="ident">list_nodes</span></span>(<span>self, filter_str=None, format_str=None)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def list_nodes(self, filter_str=None, format_str=None):
&#34;&#34;&#34;Return a listed filtered by regex match and formatted if needed.&#34;&#34;&#34;
nodes = self.config._getallnodes()
case_sensitive = self.config.config.get(&#34;case&#34;, False)
if filter_str:
flags = re.IGNORECASE if not case_sensitive else 0
nodes = [n for n in nodes if re.search(filter_str, n, flags)]
if not format_str:
return nodes
from .profile_service import ProfileService
profile_service = ProfileService(self.config)
formatted_nodes = []
for n_id in nodes:
# Use ProfileService to resolve profiles for dynamic formatting
details = self.config.getitem(n_id, extract=False)
if details:
details = profile_service.resolve_node_data(details)
name = n_id.split(&#34;@&#34;)[0]
location = n_id.partition(&#34;@&#34;)[2] or &#34;root&#34;
# Prepare context for .format() with all details
context = details.copy()
context.update({
&#34;name&#34;: name,
&#34;NAME&#34;: name.upper(),
&#34;location&#34;: location,
&#34;LOCATION&#34;: location.upper(),
})
# Add exploded uniques (id, folder, subfolder)
uniques = self.config._explode_unique(n_id)
if uniques:
context.update(uniques)
# Add uppercase versions of all keys for convenience
for k, v in list(context.items()):
if isinstance(v, str):
context[k.upper()] = v.upper()
try:
formatted_nodes.append(format_str.format(**context))
except (KeyError, IndexError, ValueError):
# Fallback to original string if format fails
formatted_nodes.append(n_id)
return formatted_nodes</code></pre>
</details>
<div class="desc"><p>Return a listed filtered by regex match and formatted if needed.</p></div>
</dd>
<dt id="connpy.services.node_service.NodeService.move_node"><code class="name flex">
<span>def <span class="ident">move_node</span></span>(<span>self, src_id, dst_id, copy=False)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def move_node(self, src_id, dst_id, copy=False):
&#34;&#34;&#34;Move or copy a node.&#34;&#34;&#34;
self._validate_node_name(dst_id)
node_data = self.config.getitem(src_id)
if not node_data:
raise NodeNotFoundError(f&#34;Source node &#39;{src_id}&#39; not found.&#34;)
if dst_id in self.config._getallnodes():
raise NodeAlreadyExistsError(f&#34;Destination node &#39;{dst_id}&#39; already exists.&#34;)
new_uniques = self.config._explode_unique(dst_id)
if not new_uniques:
raise InvalidConfigurationError(f&#34;Invalid destination format &#39;{dst_id}&#39;.&#34;)
new_node_data = node_data.copy()
new_node_data.update(new_uniques)
self.config._connections_add(**new_node_data)
if not copy:
src_uniques = self.config._explode_unique(src_id)
self.config._connections_del(**src_uniques)
self.config._saveconfig(self.config.file)</code></pre>
</details>
<div class="desc"><p>Move or copy a node.</p></div>
</dd>
<dt id="connpy.services.node_service.NodeService.update_node"><code class="name flex">
<span>def <span class="ident">update_node</span></span>(<span>self, unique_id, data)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def update_node(self, unique_id, data):
&#34;&#34;&#34;Explicitly update an existing node.&#34;&#34;&#34;
all_nodes = self.config._getallnodes()
if unique_id not in all_nodes:
raise NodeNotFoundError(f&#34;Node &#39;{unique_id}&#39; not found.&#34;)
# Ensure &#39;id&#39; is in data for config._connections_add
if &#34;id&#34; not in data:
uniques = self.config._explode_unique(unique_id)
if uniques:
data[&#34;id&#34;] = uniques[&#34;id&#34;]
# config._connections_add actually handles updates if ID exists correctly
self.config._connections_add(**data)
self.config._saveconfig(self.config.file)</code></pre>
</details>
<div class="desc"><p>Explicitly update an existing node.</p></div>
</dd>
</dl>
<h3>Inherited members</h3>
<ul class="hlist">
<li><code><b><a title="connpy.services.base.BaseService" href="base.html#connpy.services.base.BaseService">BaseService</a></b></code>:
<ul class="hlist">
<li><code><a title="connpy.services.base.BaseService.set_reserved_names" href="base.html#connpy.services.base.BaseService.set_reserved_names">set_reserved_names</a></code></li>
</ul>
</li>
</ul>
</dd>
</dl>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="connpy.services" href="index.html">connpy.services</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="connpy.services.node_service.NodeService" href="#connpy.services.node_service.NodeService">NodeService</a></code></h4>
<ul class="two-column">
<li><code><a title="connpy.services.node_service.NodeService.add_node" href="#connpy.services.node_service.NodeService.add_node">add_node</a></code></li>
<li><code><a title="connpy.services.node_service.NodeService.bulk_add" href="#connpy.services.node_service.NodeService.bulk_add">bulk_add</a></code></li>
<li><code><a title="connpy.services.node_service.NodeService.connect_node" href="#connpy.services.node_service.NodeService.connect_node">connect_node</a></code></li>
<li><code><a title="connpy.services.node_service.NodeService.delete_node" href="#connpy.services.node_service.NodeService.delete_node">delete_node</a></code></li>
<li><code><a title="connpy.services.node_service.NodeService.explode_unique" href="#connpy.services.node_service.NodeService.explode_unique">explode_unique</a></code></li>
<li><code><a title="connpy.services.node_service.NodeService.full_replace" href="#connpy.services.node_service.NodeService.full_replace">full_replace</a></code></li>
<li><code><a title="connpy.services.node_service.NodeService.generate_cache" href="#connpy.services.node_service.NodeService.generate_cache">generate_cache</a></code></li>
<li><code><a title="connpy.services.node_service.NodeService.get_inventory" href="#connpy.services.node_service.NodeService.get_inventory">get_inventory</a></code></li>
<li><code><a title="connpy.services.node_service.NodeService.get_node_details" href="#connpy.services.node_service.NodeService.get_node_details">get_node_details</a></code></li>
<li><code><a title="connpy.services.node_service.NodeService.list_folders" href="#connpy.services.node_service.NodeService.list_folders">list_folders</a></code></li>
<li><code><a title="connpy.services.node_service.NodeService.list_nodes" href="#connpy.services.node_service.NodeService.list_nodes">list_nodes</a></code></li>
<li><code><a title="connpy.services.node_service.NodeService.move_node" href="#connpy.services.node_service.NodeService.move_node">move_node</a></code></li>
<li><code><a title="connpy.services.node_service.NodeService.update_node" href="#connpy.services.node_service.NodeService.update_node">update_node</a></code></li>
</ul>
</li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.5</a>.</p>
</footer>
</body>
</html>
+663
View File
@@ -0,0 +1,663 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.5">
<title>connpy.services.plugin_service API documentation</title>
<meta name="description" content="">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source > summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible;min-width:max-content}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin:1em 0}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
/* Collapse source docstrings */
setTimeout(() => {
[...document.querySelectorAll('.hljs.language-python > .hljs-string')]
.filter(el => el.innerHTML.length > 200 && ['"""', "'''"].includes(el.innerHTML.substring(0, 3)))
.forEach(el => {
let d = document.createElement('details');
d.classList.add('hljs-string');
d.innerHTML = '<summary>"""</summary>' + el.innerHTML.substring(3);
el.replaceWith(d);
});
}, 100);
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>connpy.services.plugin_service</code></h1>
</header>
<section id="section-intro">
</section>
<section>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="connpy.services.plugin_service.PluginService"><code class="flex name class">
<span>class <span class="ident">PluginService</span></span>
<span>(</span><span>config=None)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class PluginService(BaseService):
&#34;&#34;&#34;Business logic for enabling, disabling, and listing plugins.&#34;&#34;&#34;
def list_plugins(self):
&#34;&#34;&#34;List all core and user-defined plugins with their status and hash.&#34;&#34;&#34;
import os
import hashlib
# Check for user plugins directory
plugin_dir = os.path.join(self.config.defaultdir, &#34;plugins&#34;)
# Check for core plugins directory
core_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), &#34;..&#34;, &#34;core_plugins&#34;)
all_plugin_info = {}
def get_hash(path):
try:
with open(path, &#34;rb&#34;) as f:
return hashlib.md5(f.read()).hexdigest()
except Exception:
return &#34;&#34;
# User plugins
if os.path.exists(plugin_dir):
for f in os.listdir(plugin_dir):
if f.endswith(&#34;.py&#34;):
name = f[:-3]
path = os.path.join(plugin_dir, f)
all_plugin_info[name] = {&#34;enabled&#34;: True, &#34;hash&#34;: get_hash(path)}
elif f.endswith(&#34;.py.bkp&#34;):
name = f[:-7]
all_plugin_info[name] = {&#34;enabled&#34;: False}
return all_plugin_info
def add_plugin(self, name, source_file, update=False):
&#34;&#34;&#34;Add or update a plugin from a local file.&#34;&#34;&#34;
import os
import shutil
from connpy.plugins import Plugins
if not name.isalpha() or not name.islower() or len(name) &gt; 15:
raise InvalidConfigurationError(&#34;Plugin name should be lowercase letters up to 15 characters.&#34;)
p_manager = Plugins()
# Check for bad script
error = p_manager.verify_script(source_file)
if error:
raise InvalidConfigurationError(f&#34;Invalid plugin script: {error}&#34;)
self._save_plugin_file(name, source_file, update, is_path=True)
def add_plugin_from_bytes(self, name, content, update=False):
&#34;&#34;&#34;Add or update a plugin from bytes (gRPC).&#34;&#34;&#34;
import tempfile
import os
if not name.isalpha() or not name.islower() or len(name) &gt; 15:
raise InvalidConfigurationError(&#34;Plugin name should be lowercase letters up to 15 characters.&#34;)
# Write to temp file to verify script
with tempfile.NamedTemporaryFile(suffix=&#34;.py&#34;, delete=False) as tmp:
tmp.write(content)
tmp_path = tmp.name
try:
from connpy.plugins import Plugins
p_manager = Plugins()
error = p_manager.verify_script(tmp_path)
if error:
raise InvalidConfigurationError(f&#34;Invalid plugin script: {error}&#34;)
self._save_plugin_file(name, tmp_path, update, is_path=True)
finally:
if os.path.exists(tmp_path):
os.remove(tmp_path)
def _save_plugin_file(self, name, source, update=False, is_path=True):
import os
import shutil
plugin_dir = os.path.join(self.config.defaultdir, &#34;plugins&#34;)
os.makedirs(plugin_dir, exist_ok=True)
target_file = os.path.join(plugin_dir, f&#34;{name}.py&#34;)
backup_file = f&#34;{target_file}.bkp&#34;
if not update and (os.path.exists(target_file) or os.path.exists(backup_file)):
raise InvalidConfigurationError(f&#34;Plugin &#39;{name}&#39; already exists.&#34;)
try:
if is_path:
shutil.copy2(source, target_file)
else:
with open(target_file, &#34;wb&#34;) as f:
f.write(source)
except OSError as e:
raise InvalidConfigurationError(f&#34;Failed to save plugin file: {e}&#34;)
def delete_plugin(self, name):
&#34;&#34;&#34;Remove a plugin file permanently.&#34;&#34;&#34;
import os
plugin_file = os.path.join(self.config.defaultdir, &#34;plugins&#34;, f&#34;{name}.py&#34;)
disabled_file = f&#34;{plugin_file}.bkp&#34;
deleted = False
for f in [plugin_file, disabled_file]:
if os.path.exists(f):
try:
os.remove(f)
deleted = True
except OSError as e:
raise InvalidConfigurationError(f&#34;Failed to delete plugin file &#39;{f}&#39;: {e}&#34;)
if not deleted:
raise InvalidConfigurationError(f&#34;Plugin &#39;{name}&#39; not found.&#34;)
def enable_plugin(self, name):
&#34;&#34;&#34;Activate a plugin by renaming its backup file.&#34;&#34;&#34;
import os
plugin_file = os.path.join(self.config.defaultdir, &#34;plugins&#34;, f&#34;{name}.py&#34;)
disabled_file = f&#34;{plugin_file}.bkp&#34;
if os.path.exists(plugin_file):
return False # Already enabled
if not os.path.exists(disabled_file):
raise InvalidConfigurationError(f&#34;Plugin &#39;{name}&#39; not found.&#34;)
try:
os.rename(disabled_file, plugin_file)
return True
except OSError as e:
raise InvalidConfigurationError(f&#34;Failed to enable plugin &#39;{name}&#39;: {e}&#34;)
def disable_plugin(self, name):
&#34;&#34;&#34;Deactivate a plugin by renaming it to a backup file.&#34;&#34;&#34;
import os
plugin_file = os.path.join(self.config.defaultdir, &#34;plugins&#34;, f&#34;{name}.py&#34;)
disabled_file = f&#34;{plugin_file}.bkp&#34;
if os.path.exists(disabled_file):
return False # Already disabled
if not os.path.exists(plugin_file):
raise InvalidConfigurationError(f&#34;Plugin &#39;{name}&#39; not found or is a core plugin.&#34;)
try:
os.rename(plugin_file, disabled_file)
return True
except OSError as e:
raise InvalidConfigurationError(f&#34;Failed to disable plugin &#39;{name}&#39;: {e}&#34;)
def get_plugin_source(self, name):
import os
from ..services.exceptions import InvalidConfigurationError
plugin_file = os.path.join(self.config.defaultdir, &#34;plugins&#34;, f&#34;{name}.py&#34;)
core_path = os.path.dirname(os.path.realpath(__file__)) + f&#34;/../core_plugins/{name}.py&#34;
if os.path.exists(plugin_file):
target = plugin_file
elif os.path.exists(core_path):
target = core_path
else:
raise InvalidConfigurationError(f&#34;Plugin &#39;{name}&#39; not found&#34;)
with open(target, &#34;r&#34;) as f:
return f.read()
def invoke_plugin(self, name, args_dict):
import sys, io
from argparse import Namespace
from ..services.exceptions import InvalidConfigurationError
from connpy.plugins import Plugins
class MockApp:
def __init__(self, config):
from ..core import node, nodes
from ..ai import ai
from ..services.provider import ServiceProvider
self.config = config
self.node = node
self.nodes = nodes
self.ai = ai
self.services = ServiceProvider(config, mode=&#34;local&#34;)
try:
self.nodes_list = self.services.nodes.list_nodes()
self.folders = self.services.nodes.list_folders()
self.profiles = self.services.profiles.list_profiles()
except Exception:
self.nodes_list = {}
self.folders = {}
self.profiles = {}
args = Namespace(**args_dict)
p_manager = Plugins()
import os
plugin_file = os.path.join(self.config.defaultdir, &#34;plugins&#34;, f&#34;{name}.py&#34;)
core_path = os.path.dirname(os.path.realpath(__file__)) + f&#34;/../core_plugins/{name}.py&#34;
if os.path.exists(plugin_file):
target = plugin_file
elif os.path.exists(core_path):
target = core_path
else:
raise InvalidConfigurationError(f&#34;Plugin &#39;{name}&#39; not found&#34;)
module = p_manager._import_from_path(target)
parser = module.Parser().parser if hasattr(module, &#34;Parser&#34;) else None
if &#34;__func_name__&#34; in args_dict and hasattr(module, args_dict[&#34;__func_name__&#34;]):
args.func = getattr(module, args_dict[&#34;__func_name__&#34;])
app = MockApp(self.config)
from .. import printer
from rich.console import Console
buf = io.StringIO()
old_console = printer.console
old_err_console = printer.err_console
printer.console = Console(file=buf, theme=printer.connpy_theme, force_terminal=True)
printer.err_console = Console(file=buf, theme=printer.connpy_theme, force_terminal=True)
old_stdout = sys.stdout
sys.stdout = buf
try:
if hasattr(module, &#34;Entrypoint&#34;):
module.Entrypoint(args, parser, app)
except Exception as e:
import traceback
printer.err_console.print(traceback.format_exc())
finally:
sys.stdout = old_stdout
printer.console = old_console
printer.err_console = old_err_console
for line in buf.getvalue().splitlines(keepends=True):
yield line</code></pre>
</details>
<div class="desc"><p>Business logic for enabling, disabling, and listing plugins.</p>
<p>Initialize the service.</p>
<h2 id="args">Args</h2>
<dl>
<dt><strong><code>config</code></strong></dt>
<dd>An instance of configfile (or None to instantiate a new one/use global context).</dd>
</dl></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li><a title="connpy.services.base.BaseService" href="base.html#connpy.services.base.BaseService">BaseService</a></li>
</ul>
<h3>Methods</h3>
<dl>
<dt id="connpy.services.plugin_service.PluginService.add_plugin"><code class="name flex">
<span>def <span class="ident">add_plugin</span></span>(<span>self, name, source_file, update=False)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def add_plugin(self, name, source_file, update=False):
&#34;&#34;&#34;Add or update a plugin from a local file.&#34;&#34;&#34;
import os
import shutil
from connpy.plugins import Plugins
if not name.isalpha() or not name.islower() or len(name) &gt; 15:
raise InvalidConfigurationError(&#34;Plugin name should be lowercase letters up to 15 characters.&#34;)
p_manager = Plugins()
# Check for bad script
error = p_manager.verify_script(source_file)
if error:
raise InvalidConfigurationError(f&#34;Invalid plugin script: {error}&#34;)
self._save_plugin_file(name, source_file, update, is_path=True)</code></pre>
</details>
<div class="desc"><p>Add or update a plugin from a local file.</p></div>
</dd>
<dt id="connpy.services.plugin_service.PluginService.add_plugin_from_bytes"><code class="name flex">
<span>def <span class="ident">add_plugin_from_bytes</span></span>(<span>self, name, content, update=False)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def add_plugin_from_bytes(self, name, content, update=False):
&#34;&#34;&#34;Add or update a plugin from bytes (gRPC).&#34;&#34;&#34;
import tempfile
import os
if not name.isalpha() or not name.islower() or len(name) &gt; 15:
raise InvalidConfigurationError(&#34;Plugin name should be lowercase letters up to 15 characters.&#34;)
# Write to temp file to verify script
with tempfile.NamedTemporaryFile(suffix=&#34;.py&#34;, delete=False) as tmp:
tmp.write(content)
tmp_path = tmp.name
try:
from connpy.plugins import Plugins
p_manager = Plugins()
error = p_manager.verify_script(tmp_path)
if error:
raise InvalidConfigurationError(f&#34;Invalid plugin script: {error}&#34;)
self._save_plugin_file(name, tmp_path, update, is_path=True)
finally:
if os.path.exists(tmp_path):
os.remove(tmp_path)</code></pre>
</details>
<div class="desc"><p>Add or update a plugin from bytes (gRPC).</p></div>
</dd>
<dt id="connpy.services.plugin_service.PluginService.delete_plugin"><code class="name flex">
<span>def <span class="ident">delete_plugin</span></span>(<span>self, name)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def delete_plugin(self, name):
&#34;&#34;&#34;Remove a plugin file permanently.&#34;&#34;&#34;
import os
plugin_file = os.path.join(self.config.defaultdir, &#34;plugins&#34;, f&#34;{name}.py&#34;)
disabled_file = f&#34;{plugin_file}.bkp&#34;
deleted = False
for f in [plugin_file, disabled_file]:
if os.path.exists(f):
try:
os.remove(f)
deleted = True
except OSError as e:
raise InvalidConfigurationError(f&#34;Failed to delete plugin file &#39;{f}&#39;: {e}&#34;)
if not deleted:
raise InvalidConfigurationError(f&#34;Plugin &#39;{name}&#39; not found.&#34;)</code></pre>
</details>
<div class="desc"><p>Remove a plugin file permanently.</p></div>
</dd>
<dt id="connpy.services.plugin_service.PluginService.disable_plugin"><code class="name flex">
<span>def <span class="ident">disable_plugin</span></span>(<span>self, name)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def disable_plugin(self, name):
&#34;&#34;&#34;Deactivate a plugin by renaming it to a backup file.&#34;&#34;&#34;
import os
plugin_file = os.path.join(self.config.defaultdir, &#34;plugins&#34;, f&#34;{name}.py&#34;)
disabled_file = f&#34;{plugin_file}.bkp&#34;
if os.path.exists(disabled_file):
return False # Already disabled
if not os.path.exists(plugin_file):
raise InvalidConfigurationError(f&#34;Plugin &#39;{name}&#39; not found or is a core plugin.&#34;)
try:
os.rename(plugin_file, disabled_file)
return True
except OSError as e:
raise InvalidConfigurationError(f&#34;Failed to disable plugin &#39;{name}&#39;: {e}&#34;)</code></pre>
</details>
<div class="desc"><p>Deactivate a plugin by renaming it to a backup file.</p></div>
</dd>
<dt id="connpy.services.plugin_service.PluginService.enable_plugin"><code class="name flex">
<span>def <span class="ident">enable_plugin</span></span>(<span>self, name)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def enable_plugin(self, name):
&#34;&#34;&#34;Activate a plugin by renaming its backup file.&#34;&#34;&#34;
import os
plugin_file = os.path.join(self.config.defaultdir, &#34;plugins&#34;, f&#34;{name}.py&#34;)
disabled_file = f&#34;{plugin_file}.bkp&#34;
if os.path.exists(plugin_file):
return False # Already enabled
if not os.path.exists(disabled_file):
raise InvalidConfigurationError(f&#34;Plugin &#39;{name}&#39; not found.&#34;)
try:
os.rename(disabled_file, plugin_file)
return True
except OSError as e:
raise InvalidConfigurationError(f&#34;Failed to enable plugin &#39;{name}&#39;: {e}&#34;)</code></pre>
</details>
<div class="desc"><p>Activate a plugin by renaming its backup file.</p></div>
</dd>
<dt id="connpy.services.plugin_service.PluginService.get_plugin_source"><code class="name flex">
<span>def <span class="ident">get_plugin_source</span></span>(<span>self, name)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def get_plugin_source(self, name):
import os
from ..services.exceptions import InvalidConfigurationError
plugin_file = os.path.join(self.config.defaultdir, &#34;plugins&#34;, f&#34;{name}.py&#34;)
core_path = os.path.dirname(os.path.realpath(__file__)) + f&#34;/../core_plugins/{name}.py&#34;
if os.path.exists(plugin_file):
target = plugin_file
elif os.path.exists(core_path):
target = core_path
else:
raise InvalidConfigurationError(f&#34;Plugin &#39;{name}&#39; not found&#34;)
with open(target, &#34;r&#34;) as f:
return f.read()</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.services.plugin_service.PluginService.invoke_plugin"><code class="name flex">
<span>def <span class="ident">invoke_plugin</span></span>(<span>self, name, args_dict)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def invoke_plugin(self, name, args_dict):
import sys, io
from argparse import Namespace
from ..services.exceptions import InvalidConfigurationError
from connpy.plugins import Plugins
class MockApp:
def __init__(self, config):
from ..core import node, nodes
from ..ai import ai
from ..services.provider import ServiceProvider
self.config = config
self.node = node
self.nodes = nodes
self.ai = ai
self.services = ServiceProvider(config, mode=&#34;local&#34;)
try:
self.nodes_list = self.services.nodes.list_nodes()
self.folders = self.services.nodes.list_folders()
self.profiles = self.services.profiles.list_profiles()
except Exception:
self.nodes_list = {}
self.folders = {}
self.profiles = {}
args = Namespace(**args_dict)
p_manager = Plugins()
import os
plugin_file = os.path.join(self.config.defaultdir, &#34;plugins&#34;, f&#34;{name}.py&#34;)
core_path = os.path.dirname(os.path.realpath(__file__)) + f&#34;/../core_plugins/{name}.py&#34;
if os.path.exists(plugin_file):
target = plugin_file
elif os.path.exists(core_path):
target = core_path
else:
raise InvalidConfigurationError(f&#34;Plugin &#39;{name}&#39; not found&#34;)
module = p_manager._import_from_path(target)
parser = module.Parser().parser if hasattr(module, &#34;Parser&#34;) else None
if &#34;__func_name__&#34; in args_dict and hasattr(module, args_dict[&#34;__func_name__&#34;]):
args.func = getattr(module, args_dict[&#34;__func_name__&#34;])
app = MockApp(self.config)
from .. import printer
from rich.console import Console
buf = io.StringIO()
old_console = printer.console
old_err_console = printer.err_console
printer.console = Console(file=buf, theme=printer.connpy_theme, force_terminal=True)
printer.err_console = Console(file=buf, theme=printer.connpy_theme, force_terminal=True)
old_stdout = sys.stdout
sys.stdout = buf
try:
if hasattr(module, &#34;Entrypoint&#34;):
module.Entrypoint(args, parser, app)
except Exception as e:
import traceback
printer.err_console.print(traceback.format_exc())
finally:
sys.stdout = old_stdout
printer.console = old_console
printer.err_console = old_err_console
for line in buf.getvalue().splitlines(keepends=True):
yield line</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.services.plugin_service.PluginService.list_plugins"><code class="name flex">
<span>def <span class="ident">list_plugins</span></span>(<span>self)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def list_plugins(self):
&#34;&#34;&#34;List all core and user-defined plugins with their status and hash.&#34;&#34;&#34;
import os
import hashlib
# Check for user plugins directory
plugin_dir = os.path.join(self.config.defaultdir, &#34;plugins&#34;)
# Check for core plugins directory
core_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), &#34;..&#34;, &#34;core_plugins&#34;)
all_plugin_info = {}
def get_hash(path):
try:
with open(path, &#34;rb&#34;) as f:
return hashlib.md5(f.read()).hexdigest()
except Exception:
return &#34;&#34;
# User plugins
if os.path.exists(plugin_dir):
for f in os.listdir(plugin_dir):
if f.endswith(&#34;.py&#34;):
name = f[:-3]
path = os.path.join(plugin_dir, f)
all_plugin_info[name] = {&#34;enabled&#34;: True, &#34;hash&#34;: get_hash(path)}
elif f.endswith(&#34;.py.bkp&#34;):
name = f[:-7]
all_plugin_info[name] = {&#34;enabled&#34;: False}
return all_plugin_info</code></pre>
</details>
<div class="desc"><p>List all core and user-defined plugins with their status and hash.</p></div>
</dd>
</dl>
<h3>Inherited members</h3>
<ul class="hlist">
<li><code><b><a title="connpy.services.base.BaseService" href="base.html#connpy.services.base.BaseService">BaseService</a></b></code>:
<ul class="hlist">
<li><code><a title="connpy.services.base.BaseService.set_reserved_names" href="base.html#connpy.services.base.BaseService.set_reserved_names">set_reserved_names</a></code></li>
</ul>
</li>
</ul>
</dd>
</dl>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="connpy.services" href="index.html">connpy.services</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="connpy.services.plugin_service.PluginService" href="#connpy.services.plugin_service.PluginService">PluginService</a></code></h4>
<ul class="">
<li><code><a title="connpy.services.plugin_service.PluginService.add_plugin" href="#connpy.services.plugin_service.PluginService.add_plugin">add_plugin</a></code></li>
<li><code><a title="connpy.services.plugin_service.PluginService.add_plugin_from_bytes" href="#connpy.services.plugin_service.PluginService.add_plugin_from_bytes">add_plugin_from_bytes</a></code></li>
<li><code><a title="connpy.services.plugin_service.PluginService.delete_plugin" href="#connpy.services.plugin_service.PluginService.delete_plugin">delete_plugin</a></code></li>
<li><code><a title="connpy.services.plugin_service.PluginService.disable_plugin" href="#connpy.services.plugin_service.PluginService.disable_plugin">disable_plugin</a></code></li>
<li><code><a title="connpy.services.plugin_service.PluginService.enable_plugin" href="#connpy.services.plugin_service.PluginService.enable_plugin">enable_plugin</a></code></li>
<li><code><a title="connpy.services.plugin_service.PluginService.get_plugin_source" href="#connpy.services.plugin_service.PluginService.get_plugin_source">get_plugin_source</a></code></li>
<li><code><a title="connpy.services.plugin_service.PluginService.invoke_plugin" href="#connpy.services.plugin_service.PluginService.invoke_plugin">invoke_plugin</a></code></li>
<li><code><a title="connpy.services.plugin_service.PluginService.list_plugins" href="#connpy.services.plugin_service.PluginService.list_plugins">list_plugins</a></code></li>
</ul>
</li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.5</a>.</p>
</footer>
</body>
</html>
+435
View File
@@ -0,0 +1,435 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.5">
<title>connpy.services.profile_service API documentation</title>
<meta name="description" content="">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source > summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible;min-width:max-content}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin:1em 0}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
/* Collapse source docstrings */
setTimeout(() => {
[...document.querySelectorAll('.hljs.language-python > .hljs-string')]
.filter(el => el.innerHTML.length > 200 && ['"""', "'''"].includes(el.innerHTML.substring(0, 3)))
.forEach(el => {
let d = document.createElement('details');
d.classList.add('hljs-string');
d.innerHTML = '<summary>"""</summary>' + el.innerHTML.substring(3);
el.replaceWith(d);
});
}, 100);
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>connpy.services.profile_service</code></h1>
</header>
<section id="section-intro">
</section>
<section>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="connpy.services.profile_service.ProfileService"><code class="flex name class">
<span>class <span class="ident">ProfileService</span></span>
<span>(</span><span>config=None)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class ProfileService(BaseService):
&#34;&#34;&#34;Business logic for node profiles management.&#34;&#34;&#34;
def list_profiles(self, filter_str=None):
&#34;&#34;&#34;List all profile names, optionally filtered.&#34;&#34;&#34;
profiles = list(self.config.profiles.keys())
case_sensitive = self.config.config.get(&#34;case&#34;, False)
if filter_str:
if not case_sensitive:
f_str = filter_str.lower()
return [p for p in profiles if f_str in p.lower()]
else:
return [p for p in profiles if filter_str in p]
return profiles
def get_profile(self, name, resolve=True):
&#34;&#34;&#34;Get the profile dictionary, optionally resolved.&#34;&#34;&#34;
profile = self.config.profiles.get(name)
if not profile:
raise ProfileNotFoundError(f&#34;Profile &#39;{name}&#39; not found.&#34;)
if resolve:
return self.resolve_node_data(profile)
return profile
def add_profile(self, name, data):
&#34;&#34;&#34;Add a new profile.&#34;&#34;&#34;
if name in self.config.profiles:
raise ProfileAlreadyExistsError(f&#34;Profile &#39;{name}&#39; already exists.&#34;)
# Filter data to match _profiles_add signature and ensure id is passed
allowed_keys = {&#34;host&#34;, &#34;options&#34;, &#34;logs&#34;, &#34;password&#34;, &#34;port&#34;, &#34;protocol&#34;, &#34;user&#34;, &#34;tags&#34;, &#34;jumphost&#34;}
filtered_data = {k: v for k, v in data.items() if k in allowed_keys}
self.config._profiles_add(id=name, **filtered_data)
self.config._saveconfig(self.config.file)
def resolve_node_data(self, node_data):
&#34;&#34;&#34;Resolve profile references (@profile) in node data and handle inheritance.&#34;&#34;&#34;
resolved = node_data.copy()
# 1. Identify all referenced profiles to support inheritance
referenced_profiles = []
for value in resolved.values():
if isinstance(value, str) and value.startswith(&#34;@&#34;):
referenced_profiles.append(value[1:])
elif isinstance(value, list):
for item in value:
if isinstance(item, str) and item.startswith(&#34;@&#34;):
referenced_profiles.append(item[1:])
# 2. Resolve explicit references
for key, value in resolved.items():
if isinstance(value, str) and value.startswith(&#34;@&#34;):
profile_name = value[1:]
try:
profile = self.get_profile(profile_name, resolve=True)
resolved[key] = profile.get(key, &#34;&#34;)
except ProfileNotFoundError:
resolved[key] = &#34;&#34;
elif isinstance(value, list):
resolved_list = []
for item in value:
if isinstance(item, str) and item.startswith(&#34;@&#34;):
profile_name = item[1:]
try:
profile = self.get_profile(profile_name, resolve=True)
if &#34;password&#34; in profile:
resolved_list.append(profile[&#34;password&#34;])
except ProfileNotFoundError:
pass
else:
resolved_list.append(item)
resolved[key] = resolved_list
# 3. Inheritance: Fill empty keys from the first referenced profile
if referenced_profiles:
base_profile_name = referenced_profiles[0]
try:
base_profile = self.get_profile(base_profile_name, resolve=True)
for key, value in base_profile.items():
# Fill if key is missing or empty
if key not in resolved or resolved[key] == &#34;&#34; or resolved[key] == [] or resolved[key] is None:
resolved[key] = value
except ProfileNotFoundError:
pass
# 4. Handle default protocol
if resolved.get(&#34;protocol&#34;) == &#34;&#34; or resolved.get(&#34;protocol&#34;) is None:
try:
default_profile = self.get_profile(&#34;default&#34;, resolve=True)
resolved[&#34;protocol&#34;] = default_profile.get(&#34;protocol&#34;, &#34;ssh&#34;)
except ProfileNotFoundError:
resolved[&#34;protocol&#34;] = &#34;ssh&#34;
return resolved
def delete_profile(self, name):
&#34;&#34;&#34;Delete an existing profile, with safety checks.&#34;&#34;&#34;
if name not in self.config.profiles:
raise ProfileNotFoundError(f&#34;Profile &#39;{name}&#39; not found.&#34;)
if name == &#34;default&#34;:
raise InvalidConfigurationError(&#34;Cannot delete the &#39;default&#39; profile.&#34;)
used_by = self.config._profileused(name)
if used_by:
# We return the list of nodes using it so the UI can inform the user
raise InvalidConfigurationError(f&#34;Profile &#39;{name}&#39; is used by nodes: {&#39;, &#39;.join(used_by)}&#34;)
self.config._profiles_del(id=name)
self.config._saveconfig(self.config.file)
def update_profile(self, name, data):
&#34;&#34;&#34;Update an existing profile.&#34;&#34;&#34;
if name not in self.config.profiles:
raise ProfileNotFoundError(f&#34;Profile &#39;{name}&#39; not found.&#34;)
# Merge with existing data
existing = self.get_profile(name, resolve=False)
updated_data = existing.copy()
updated_data.update(data)
# Filter data to match _profiles_add signature
allowed_keys = {&#34;host&#34;, &#34;options&#34;, &#34;logs&#34;, &#34;password&#34;, &#34;port&#34;, &#34;protocol&#34;, &#34;user&#34;, &#34;tags&#34;, &#34;jumphost&#34;}
filtered_data = {k: v for k, v in updated_data.items() if k in allowed_keys}
self.config._profiles_add(id=name, **filtered_data)
self.config._saveconfig(self.config.file)</code></pre>
</details>
<div class="desc"><p>Business logic for node profiles management.</p>
<p>Initialize the service.</p>
<h2 id="args">Args</h2>
<dl>
<dt><strong><code>config</code></strong></dt>
<dd>An instance of configfile (or None to instantiate a new one/use global context).</dd>
</dl></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li><a title="connpy.services.base.BaseService" href="base.html#connpy.services.base.BaseService">BaseService</a></li>
</ul>
<h3>Methods</h3>
<dl>
<dt id="connpy.services.profile_service.ProfileService.add_profile"><code class="name flex">
<span>def <span class="ident">add_profile</span></span>(<span>self, name, data)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def add_profile(self, name, data):
&#34;&#34;&#34;Add a new profile.&#34;&#34;&#34;
if name in self.config.profiles:
raise ProfileAlreadyExistsError(f&#34;Profile &#39;{name}&#39; already exists.&#34;)
# Filter data to match _profiles_add signature and ensure id is passed
allowed_keys = {&#34;host&#34;, &#34;options&#34;, &#34;logs&#34;, &#34;password&#34;, &#34;port&#34;, &#34;protocol&#34;, &#34;user&#34;, &#34;tags&#34;, &#34;jumphost&#34;}
filtered_data = {k: v for k, v in data.items() if k in allowed_keys}
self.config._profiles_add(id=name, **filtered_data)
self.config._saveconfig(self.config.file)</code></pre>
</details>
<div class="desc"><p>Add a new profile.</p></div>
</dd>
<dt id="connpy.services.profile_service.ProfileService.delete_profile"><code class="name flex">
<span>def <span class="ident">delete_profile</span></span>(<span>self, name)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def delete_profile(self, name):
&#34;&#34;&#34;Delete an existing profile, with safety checks.&#34;&#34;&#34;
if name not in self.config.profiles:
raise ProfileNotFoundError(f&#34;Profile &#39;{name}&#39; not found.&#34;)
if name == &#34;default&#34;:
raise InvalidConfigurationError(&#34;Cannot delete the &#39;default&#39; profile.&#34;)
used_by = self.config._profileused(name)
if used_by:
# We return the list of nodes using it so the UI can inform the user
raise InvalidConfigurationError(f&#34;Profile &#39;{name}&#39; is used by nodes: {&#39;, &#39;.join(used_by)}&#34;)
self.config._profiles_del(id=name)
self.config._saveconfig(self.config.file)</code></pre>
</details>
<div class="desc"><p>Delete an existing profile, with safety checks.</p></div>
</dd>
<dt id="connpy.services.profile_service.ProfileService.get_profile"><code class="name flex">
<span>def <span class="ident">get_profile</span></span>(<span>self, name, resolve=True)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def get_profile(self, name, resolve=True):
&#34;&#34;&#34;Get the profile dictionary, optionally resolved.&#34;&#34;&#34;
profile = self.config.profiles.get(name)
if not profile:
raise ProfileNotFoundError(f&#34;Profile &#39;{name}&#39; not found.&#34;)
if resolve:
return self.resolve_node_data(profile)
return profile</code></pre>
</details>
<div class="desc"><p>Get the profile dictionary, optionally resolved.</p></div>
</dd>
<dt id="connpy.services.profile_service.ProfileService.list_profiles"><code class="name flex">
<span>def <span class="ident">list_profiles</span></span>(<span>self, filter_str=None)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def list_profiles(self, filter_str=None):
&#34;&#34;&#34;List all profile names, optionally filtered.&#34;&#34;&#34;
profiles = list(self.config.profiles.keys())
case_sensitive = self.config.config.get(&#34;case&#34;, False)
if filter_str:
if not case_sensitive:
f_str = filter_str.lower()
return [p for p in profiles if f_str in p.lower()]
else:
return [p for p in profiles if filter_str in p]
return profiles</code></pre>
</details>
<div class="desc"><p>List all profile names, optionally filtered.</p></div>
</dd>
<dt id="connpy.services.profile_service.ProfileService.resolve_node_data"><code class="name flex">
<span>def <span class="ident">resolve_node_data</span></span>(<span>self, node_data)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def resolve_node_data(self, node_data):
&#34;&#34;&#34;Resolve profile references (@profile) in node data and handle inheritance.&#34;&#34;&#34;
resolved = node_data.copy()
# 1. Identify all referenced profiles to support inheritance
referenced_profiles = []
for value in resolved.values():
if isinstance(value, str) and value.startswith(&#34;@&#34;):
referenced_profiles.append(value[1:])
elif isinstance(value, list):
for item in value:
if isinstance(item, str) and item.startswith(&#34;@&#34;):
referenced_profiles.append(item[1:])
# 2. Resolve explicit references
for key, value in resolved.items():
if isinstance(value, str) and value.startswith(&#34;@&#34;):
profile_name = value[1:]
try:
profile = self.get_profile(profile_name, resolve=True)
resolved[key] = profile.get(key, &#34;&#34;)
except ProfileNotFoundError:
resolved[key] = &#34;&#34;
elif isinstance(value, list):
resolved_list = []
for item in value:
if isinstance(item, str) and item.startswith(&#34;@&#34;):
profile_name = item[1:]
try:
profile = self.get_profile(profile_name, resolve=True)
if &#34;password&#34; in profile:
resolved_list.append(profile[&#34;password&#34;])
except ProfileNotFoundError:
pass
else:
resolved_list.append(item)
resolved[key] = resolved_list
# 3. Inheritance: Fill empty keys from the first referenced profile
if referenced_profiles:
base_profile_name = referenced_profiles[0]
try:
base_profile = self.get_profile(base_profile_name, resolve=True)
for key, value in base_profile.items():
# Fill if key is missing or empty
if key not in resolved or resolved[key] == &#34;&#34; or resolved[key] == [] or resolved[key] is None:
resolved[key] = value
except ProfileNotFoundError:
pass
# 4. Handle default protocol
if resolved.get(&#34;protocol&#34;) == &#34;&#34; or resolved.get(&#34;protocol&#34;) is None:
try:
default_profile = self.get_profile(&#34;default&#34;, resolve=True)
resolved[&#34;protocol&#34;] = default_profile.get(&#34;protocol&#34;, &#34;ssh&#34;)
except ProfileNotFoundError:
resolved[&#34;protocol&#34;] = &#34;ssh&#34;
return resolved</code></pre>
</details>
<div class="desc"><p>Resolve profile references (@profile) in node data and handle inheritance.</p></div>
</dd>
<dt id="connpy.services.profile_service.ProfileService.update_profile"><code class="name flex">
<span>def <span class="ident">update_profile</span></span>(<span>self, name, data)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def update_profile(self, name, data):
&#34;&#34;&#34;Update an existing profile.&#34;&#34;&#34;
if name not in self.config.profiles:
raise ProfileNotFoundError(f&#34;Profile &#39;{name}&#39; not found.&#34;)
# Merge with existing data
existing = self.get_profile(name, resolve=False)
updated_data = existing.copy()
updated_data.update(data)
# Filter data to match _profiles_add signature
allowed_keys = {&#34;host&#34;, &#34;options&#34;, &#34;logs&#34;, &#34;password&#34;, &#34;port&#34;, &#34;protocol&#34;, &#34;user&#34;, &#34;tags&#34;, &#34;jumphost&#34;}
filtered_data = {k: v for k, v in updated_data.items() if k in allowed_keys}
self.config._profiles_add(id=name, **filtered_data)
self.config._saveconfig(self.config.file)</code></pre>
</details>
<div class="desc"><p>Update an existing profile.</p></div>
</dd>
</dl>
<h3>Inherited members</h3>
<ul class="hlist">
<li><code><b><a title="connpy.services.base.BaseService" href="base.html#connpy.services.base.BaseService">BaseService</a></b></code>:
<ul class="hlist">
<li><code><a title="connpy.services.base.BaseService.set_reserved_names" href="base.html#connpy.services.base.BaseService.set_reserved_names">set_reserved_names</a></code></li>
</ul>
</li>
</ul>
</dd>
</dl>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="connpy.services" href="index.html">connpy.services</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="connpy.services.profile_service.ProfileService" href="#connpy.services.profile_service.ProfileService">ProfileService</a></code></h4>
<ul class="two-column">
<li><code><a title="connpy.services.profile_service.ProfileService.add_profile" href="#connpy.services.profile_service.ProfileService.add_profile">add_profile</a></code></li>
<li><code><a title="connpy.services.profile_service.ProfileService.delete_profile" href="#connpy.services.profile_service.ProfileService.delete_profile">delete_profile</a></code></li>
<li><code><a title="connpy.services.profile_service.ProfileService.get_profile" href="#connpy.services.profile_service.ProfileService.get_profile">get_profile</a></code></li>
<li><code><a title="connpy.services.profile_service.ProfileService.list_profiles" href="#connpy.services.profile_service.ProfileService.list_profiles">list_profiles</a></code></li>
<li><code><a title="connpy.services.profile_service.ProfileService.resolve_node_data" href="#connpy.services.profile_service.ProfileService.resolve_node_data">resolve_node_data</a></code></li>
<li><code><a title="connpy.services.profile_service.ProfileService.update_profile" href="#connpy.services.profile_service.ProfileService.update_profile">update_profile</a></code></li>
</ul>
</li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.5</a>.</p>
</footer>
</body>
</html>
+170
View File
@@ -0,0 +1,170 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.5">
<title>connpy.services.provider API documentation</title>
<meta name="description" content="">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source > summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible;min-width:max-content}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin:1em 0}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
/* Collapse source docstrings */
setTimeout(() => {
[...document.querySelectorAll('.hljs.language-python > .hljs-string')]
.filter(el => el.innerHTML.length > 200 && ['"""', "'''"].includes(el.innerHTML.substring(0, 3)))
.forEach(el => {
let d = document.createElement('details');
d.classList.add('hljs-string');
d.innerHTML = '<summary>"""</summary>' + el.innerHTML.substring(3);
el.replaceWith(d);
});
}, 100);
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>connpy.services.provider</code></h1>
</header>
<section id="section-intro">
</section>
<section>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="connpy.services.provider.RemoteStub"><code class="flex name class">
<span>class <span class="ident">RemoteStub</span></span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class RemoteStub:
def __getattr__(self, name):
raise NotImplementedError(
&#34;Remote mode (gRPC) is not yet available. &#34;
&#34;Use local mode or wait for the gRPC implementation.&#34;
)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.services.provider.ServiceProvider"><code class="flex name class">
<span>class <span class="ident">ServiceProvider</span></span>
<span>(</span><span>config, mode='local', remote_host=None)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class ServiceProvider:
&#34;&#34;&#34;Dynamic service backend. Transparently provides local or remote services.&#34;&#34;&#34;
def __init__(self, config, mode=&#34;local&#34;, remote_host=None):
self.mode = mode
self.config = config
self.remote_host = remote_host
if mode == &#34;local&#34;:
self._init_local()
elif mode == &#34;remote&#34;:
self._init_remote()
else:
raise ValueError(f&#34;Unknown service mode: {mode}&#34;)
def _init_local(self):
from .node_service import NodeService
from .profile_service import ProfileService
from .config_service import ConfigService
from .plugin_service import PluginService
from .ai_service import AIService
from .system_service import SystemService
from .execution_service import ExecutionService
from .import_export_service import ImportExportService
from .context_service import ContextService
from .sync_service import SyncService
self.nodes = NodeService(self.config)
self.profiles = ProfileService(self.config)
self.config_svc = ConfigService(self.config)
self.plugins = PluginService(self.config)
self.ai = AIService(self.config)
self.system = SystemService(self.config)
self.execution = ExecutionService(self.config)
self.import_export = ImportExportService(self.config)
self.context = ContextService(self.config)
self.sync = SyncService(self.config)
def _init_remote(self):
# Allow ConfigService to work locally so the user can revert the mode
from .config_service import ConfigService
from .context_service import ContextService
from .sync_service import SyncService
self.config_svc = ConfigService(self.config)
self.context = ContextService(self.config)
self.sync = SyncService(self.config)
if not self.remote_host:
raise InvalidConfigurationError(&#34;Remote host must be specified in remote mode&#34;)
import grpc
from ..grpc.stubs import NodeStub, ProfileStub, PluginStub, AIStub, ExecutionStub, ImportExportStub, SystemStub
channel = grpc.insecure_channel(self.remote_host)
self.nodes = NodeStub(channel, remote_host=self.remote_host, config=self.config)
self.profiles = ProfileStub(channel, remote_host=self.remote_host, node_stub=self.nodes)
self.plugins = PluginStub(channel, remote_host=self.remote_host)
self.ai = AIStub(channel, remote_host=self.remote_host)
self.system = SystemStub(channel, remote_host=self.remote_host)
self.execution = ExecutionStub(channel, remote_host=self.remote_host)
self.import_export = ImportExportStub(channel, remote_host=self.remote_host)</code></pre>
</details>
<div class="desc"><p>Dynamic service backend. Transparently provides local or remote services.</p></div>
</dd>
</dl>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="connpy.services" href="index.html">connpy.services</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="connpy.services.provider.RemoteStub" href="#connpy.services.provider.RemoteStub">RemoteStub</a></code></h4>
</li>
<li>
<h4><code><a title="connpy.services.provider.ServiceProvider" href="#connpy.services.provider.ServiceProvider">ServiceProvider</a></code></h4>
</li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.5</a>.</p>
</footer>
</body>
</html>
+970
View File
@@ -0,0 +1,970 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.5">
<title>connpy.services.sync_service API documentation</title>
<meta name="description" content="">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source > summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible;min-width:max-content}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin:1em 0}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
/* Collapse source docstrings */
setTimeout(() => {
[...document.querySelectorAll('.hljs.language-python > .hljs-string')]
.filter(el => el.innerHTML.length > 200 && ['"""', "'''"].includes(el.innerHTML.substring(0, 3)))
.forEach(el => {
let d = document.createElement('details');
d.classList.add('hljs-string');
d.innerHTML = '<summary>"""</summary>' + el.innerHTML.substring(3);
el.replaceWith(d);
});
}, 100);
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>connpy.services.sync_service</code></h1>
</header>
<section id="section-intro">
</section>
<section>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="connpy.services.sync_service.SyncService"><code class="flex name class">
<span>class <span class="ident">SyncService</span></span>
<span>(</span><span>config)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class SyncService(BaseService):
&#34;&#34;&#34;Business logic for Google Drive synchronization.&#34;&#34;&#34;
def __init__(self, config):
super().__init__(config)
self.scopes = [&#39;https://www.googleapis.com/auth/drive.appdata&#39;]
self.token_file = os.path.join(self.config.defaultdir, &#34;gtoken.json&#34;)
# Embedded OAuth config
self.client_config = {
&#34;installed&#34;: {
&#34;client_id&#34;: &#34;559598250648-cr189kfrga2il1a6d6nkaspq0a9pn5vv.&#34; + &#34;apps.googleusercontent.com&#34;,
&#34;project_id&#34;: &#34;celtic-surface-420323&#34;,
&#34;auth_uri&#34;: &#34;https://accounts.google.com/o/oauth2/auth&#34;,
&#34;token_uri&#34;: &#34;https://oauth2.googleapis.com/token&#34;,
&#34;auth_provider_x509_cert_url&#34;: &#34;https://www.googleapis.com/oauth2/v1/certs&#34;,
&#34;client_secret&#34;: &#34;GOCSPX-&#34; + &#34;VVfOSrJLPU90Pl0g7aAXM9GK2xPE&#34;,
&#34;redirect_uris&#34;: [&#34;http://localhost&#34;]
}
}
# Sync status from config
self.sync_enabled = self.config.config.get(&#34;sync&#34;, False)
self.sync_remote = self.config.config.get(&#34;sync_remote&#34;, False)
def login(self):
&#34;&#34;&#34;Authenticate with Google Drive.&#34;&#34;&#34;
creds = None
if os.path.exists(self.token_file):
creds = Credentials.from_authorized_user_file(self.token_file, self.scopes)
try:
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_config(self.client_config, self.scopes)
creds = flow.run_local_server(port=0, access_type=&#39;offline&#39;)
with open(self.token_file, &#39;w&#39;) as token:
token.write(creds.to_json())
printer.success(&#34;Logged in successfully.&#34;)
return True
except RefreshError:
if os.path.exists(self.token_file):
os.remove(self.token_file)
printer.warning(&#34;Existing token was invalid and has been removed. Please log in again.&#34;)
return False
except Exception as e:
printer.error(f&#34;Login failed: {e}&#34;)
return False
def logout(self):
&#34;&#34;&#34;Remove Google Drive credentials.&#34;&#34;&#34;
if os.path.exists(self.token_file):
os.remove(self.token_file)
printer.success(&#34;Logged out successfully.&#34;)
else:
printer.info(&#34;No credentials file found. Already logged out.&#34;)
def get_credentials(self):
&#34;&#34;&#34;Get valid credentials, refreshing if necessary.&#34;&#34;&#34;
if os.path.exists(self.token_file):
creds = Credentials.from_authorized_user_file(self.token_file, self.scopes)
else:
return None
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
try:
creds.refresh(Request())
except RefreshError:
return None
else:
return None
return creds
def check_login_status(self):
&#34;&#34;&#34;Check if logged in to Google Drive.&#34;&#34;&#34;
if os.path.exists(self.token_file):
creds = Credentials.from_authorized_user_file(self.token_file)
if creds and creds.expired and creds.refresh_token:
try:
creds.refresh(Request())
except RefreshError:
pass
return True if creds.valid else &#34;Invalid&#34;
return False
def list_backups(self):
&#34;&#34;&#34;List files in Google Drive appDataFolder.&#34;&#34;&#34;
creds = self.get_credentials()
if not creds:
printer.error(&#34;Not logged in to Google Drive.&#34;)
return []
try:
service = build(&#34;drive&#34;, &#34;v3&#34;, credentials=creds)
response = service.files().list(
spaces=&#34;appDataFolder&#34;,
fields=&#34;files(id, name, appProperties)&#34;,
pageSize=10,
).execute()
files_info = []
for file in response.get(&#34;files&#34;, []):
files_info.append({
&#34;name&#34;: file.get(&#34;name&#34;),
&#34;id&#34;: file.get(&#34;id&#34;),
&#34;date&#34;: file.get(&#34;appProperties&#34;, {}).get(&#34;date&#34;),
&#34;timestamp&#34;: file.get(&#34;appProperties&#34;, {}).get(&#34;timestamp&#34;)
})
return files_info
except HttpError as error:
printer.error(f&#34;Google Drive API error: {error}&#34;)
return []
def compress_and_upload(self, remote_data=None):
&#34;&#34;&#34;Compress config and upload to Drive.&#34;&#34;&#34;
timestamp = int(time.time() * 1000)
with tempfile.TemporaryDirectory() as tmp_dir:
zip_path = os.path.join(tmp_dir, f&#34;connpy-backup-{timestamp}.zip&#34;)
with zipfile.ZipFile(zip_path, &#39;w&#39;, zipfile.ZIP_DEFLATED) as zipf:
# If we have remote data, we create a virtual config file
if remote_data:
config_tmp = os.path.join(tmp_dir, &#34;config.yaml&#34;)
with open(config_tmp, &#39;w&#39;) as f:
yaml.dump(remote_data, f, default_flow_style=False)
zipf.write(config_tmp, &#34;config.yaml&#34;)
else:
# Legacy behavior: use local file
zipf.write(self.config.file, os.path.basename(self.config.file))
# Always include the key if it exists
if os.path.exists(self.config.key):
zipf.write(self.config.key, &#34;.osk&#34;)
# Manage retention (max 10 backups)
backups = self.list_backups()
if len(backups) &gt;= 10:
oldest = min(backups, key=lambda x: x[&#39;timestamp&#39;] or &#39;0&#39;)
self.delete_backup(oldest[&#39;id&#39;])
# Upload
return self.upload_file(zip_path, timestamp)
def upload_file(self, file_path, timestamp):
&#34;&#34;&#34;Internal method to upload to Drive.&#34;&#34;&#34;
creds = self.get_credentials()
if not creds: return False
service = build(&#39;drive&#39;, &#39;v3&#39;, credentials=creds)
date_str = datetime.fromtimestamp(timestamp/1000).strftime(&#39;%Y-%m-%d %H:%M:%S&#39;)
file_metadata = {
&#39;name&#39;: os.path.basename(file_path),
&#39;parents&#39;: [&#34;appDataFolder&#34;],
&#39;appProperties&#39;: {
&#39;timestamp&#39;: str(timestamp),
&#39;date&#39;: date_str
}
}
media = MediaFileUpload(file_path)
try:
service.files().create(body=file_metadata, media_body=media, fields=&#39;id&#39;).execute()
printer.success(&#34;Backup uploaded to Google Drive.&#34;)
return True
except Exception as e:
printer.error(f&#34;Upload failed: {e}&#34;)
return False
def delete_backup(self, file_id):
&#34;&#34;&#34;Delete a backup from Drive.&#34;&#34;&#34;
creds = self.get_credentials()
if not creds: return False
try:
service = build(&#34;drive&#34;, &#34;v3&#34;, credentials=creds)
service.files().delete(fileId=file_id).execute()
return True
except Exception as e:
printer.error(f&#34;Delete failed: {e}&#34;)
return False
def restore_backup(self, file_id=None, restore_config=True, restore_nodes=True, app_instance=None):
&#34;&#34;&#34;Download and analyze a backup for restoration.&#34;&#34;&#34;
backups = self.list_backups()
if not backups:
printer.error(&#34;No backups found.&#34;)
return None
if file_id:
selected = next((f for f in backups if f[&#39;id&#39;] == file_id), None)
if not selected:
printer.error(f&#34;Backup {file_id} not found.&#34;)
return None
else:
selected = max(backups, key=lambda x: x[&#39;timestamp&#39;] or &#39;0&#39;)
with tempfile.TemporaryDirectory() as tmp_dir:
zip_path = os.path.join(tmp_dir, &#39;restore.zip&#39;)
if self.download_file(selected[&#39;id&#39;], zip_path):
return self.perform_restore(zip_path, restore_config, restore_nodes, app_instance)
return False
def download_file(self, file_id, dest):
&#34;&#34;&#34;Internal method to download from Drive.&#34;&#34;&#34;
creds = self.get_credentials()
if not creds: return False
try:
service = build(&#39;drive&#39;, &#39;v3&#39;, credentials=creds)
request = service.files().get_media(fileId=file_id)
with io.FileIO(dest, mode=&#39;wb&#39;) as fh:
downloader = MediaIoBaseDownload(fh, request)
done = False
while not done:
_, done = downloader.next_chunk()
return True
except Exception as e:
printer.error(f&#34;Download failed: {e}&#34;)
return False
def perform_restore(self, zip_path, restore_config=True, restore_nodes=True, app_instance=None):
&#34;&#34;&#34;Execute the actual restoration of files or remote nodes.&#34;&#34;&#34;
try:
with zipfile.ZipFile(zip_path, &#39;r&#39;) as zipf:
names = zipf.namelist()
dest_dir = os.path.dirname(self.config.file)
# We need to read the config content from zip to decide what to do
backup_data = {}
config_filename = &#34;config.yaml&#34; if &#34;config.yaml&#34; in names else (&#34;config.json&#34; if &#34;config.json&#34; in names else None)
if config_filename:
with zipf.open(config_filename) as f:
backup_data = yaml.safe_load(f)
# 1. Restore Key (.osk) - Part of config identity
if restore_config and &#34;.osk&#34; in names:
zipf.extract(&#34;.osk&#34;, os.path.dirname(self.config.key))
# 2. Restore Config (Local Settings)
if restore_config and backup_data:
local_config = self.config.config.copy()
# Capture current connectivity settings to preserve them
current_mode = local_config.get(&#34;service_mode&#34;, &#34;local&#34;)
current_remote = local_config.get(&#34;remote_host&#34;)
if &#34;config&#34; in backup_data:
local_config.update(backup_data[&#34;config&#34;])
# Restore connectivity settings - we don&#39;t want a restore to
# accidentally switch us between local and remote and break connectivity
local_config[&#34;service_mode&#34;] = current_mode
if current_remote:
local_config[&#34;remote_host&#34;] = current_remote
self.config.config = local_config
self.config._saveconfig(self.config.file)
# 3. Restore Nodes and Profiles
if restore_nodes and backup_data:
connections = backup_data.get(&#34;connections&#34;, {})
profiles = backup_data.get(&#34;profiles&#34;, {})
if app_instance and app_instance.services.mode == &#34;remote&#34;:
# Push to Remote via gRPC
app_instance.services.nodes.full_replace(connections, profiles)
else:
# Restore to Local config file
self.config.connections = connections
self.config.profiles = profiles
self.config._saveconfig(self.config.file)
# Clear caches
for f in [self.config.cachefile, self.config.fzf_cachefile]:
if os.path.exists(f): os.remove(f)
return True
except Exception as e:
printer.error(f&#34;Restoration failed: {e}&#34;)
return False
def analyze_backup_content(self, file_id=None):
&#34;&#34;&#34;Analyze a backup without restoring to provide info for confirmation.&#34;&#34;&#34;
backups = self.list_backups()
if not backups: return None
selected = next((f for f in backups if f[&#39;id&#39;] == file_id), None) if file_id else max(backups, key=lambda x: x[&#39;timestamp&#39;] or &#39;0&#39;)
with tempfile.TemporaryDirectory() as tmp_dir:
zip_path = os.path.join(tmp_dir, &#39;analyze.zip&#39;)
if self.download_file(selected[&#39;id&#39;], zip_path):
with zipfile.ZipFile(zip_path, &#39;r&#39;) as zipf:
names = zipf.namelist()
config_filename = &#34;config.yaml&#34; if &#34;config.yaml&#34; in names else (&#34;config.json&#34; if &#34;config.json&#34; in names else None)
if config_filename:
with zipf.open(config_filename) as f:
data = yaml.safe_load(f)
connections = data.get(&#34;connections&#34;, {})
# Accurate recursive count
nodes_count = 0
folders_count = 0
# Layer 1
for k, v in connections.items():
if isinstance(v, dict):
if v.get(&#34;type&#34;) == &#34;connection&#34;:
nodes_count += 1
elif v.get(&#34;type&#34;) == &#34;folder&#34;:
folders_count += 1
# Layer 2
for k2, v2 in v.items():
if isinstance(v2, dict):
if v2.get(&#34;type&#34;) == &#34;connection&#34;:
nodes_count += 1
elif v2.get(&#34;type&#34;) == &#34;subfolder&#34;:
folders_count += 1
# Layer 3
for k3, v3 in v2.items():
if isinstance(v3, dict) and v3.get(&#34;type&#34;) == &#34;connection&#34;:
nodes_count += 1
return {
&#34;nodes&#34;: nodes_count,
&#34;folders&#34;: folders_count,
&#34;profiles&#34;: len(data.get(&#34;profiles&#34;, {})),
&#34;has_config&#34;: &#34;config&#34; in data,
&#34;has_key&#34;: &#34;.osk&#34; in names
}
return None
def perform_sync(self, app_instance):
&#34;&#34;&#34;Background sync logic.&#34;&#34;&#34;
# Always check current config state
sync_enabled = self.config.config.get(&#34;sync&#34;, False)
sync_remote = self.config.config.get(&#34;sync_remote&#34;, False)
if not sync_enabled: return
printer.info(&#34;Triggering auto-sync...&#34;)
if self.check_login_status() != True:
printer.warning(&#34;Auto-sync: Not logged in to Google Drive.&#34;)
return
remote_data = None
if sync_remote and app_instance.services.mode == &#34;remote&#34;:
try:
inventory = app_instance.services.nodes.get_inventory()
# Merge with local settings
local_settings = app_instance.services.config_svc.get_settings()
local_settings.pop(&#34;configfolder&#34;, None)
# Maintain proper config structure: {config: {}, connections: {}, profiles: {}}
remote_data = {
&#34;config&#34;: local_settings,
&#34;connections&#34;: inventory.get(&#34;connections&#34;, {}),
&#34;profiles&#34;: inventory.get(&#34;profiles&#34;, {})
}
except Exception as e:
printer.warning(f&#34;Could not fetch remote inventory for sync: {e}&#34;)
# Run in thread to not block CLI
threading.Thread(
target=self.compress_and_upload,
args=(remote_data,)
).start()</code></pre>
</details>
<div class="desc"><p>Business logic for Google Drive synchronization.</p>
<p>Initialize the service.</p>
<h2 id="args">Args</h2>
<dl>
<dt><strong><code>config</code></strong></dt>
<dd>An instance of configfile (or None to instantiate a new one/use global context).</dd>
</dl></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li><a title="connpy.services.base.BaseService" href="base.html#connpy.services.base.BaseService">BaseService</a></li>
</ul>
<h3>Methods</h3>
<dl>
<dt id="connpy.services.sync_service.SyncService.analyze_backup_content"><code class="name flex">
<span>def <span class="ident">analyze_backup_content</span></span>(<span>self, file_id=None)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def analyze_backup_content(self, file_id=None):
&#34;&#34;&#34;Analyze a backup without restoring to provide info for confirmation.&#34;&#34;&#34;
backups = self.list_backups()
if not backups: return None
selected = next((f for f in backups if f[&#39;id&#39;] == file_id), None) if file_id else max(backups, key=lambda x: x[&#39;timestamp&#39;] or &#39;0&#39;)
with tempfile.TemporaryDirectory() as tmp_dir:
zip_path = os.path.join(tmp_dir, &#39;analyze.zip&#39;)
if self.download_file(selected[&#39;id&#39;], zip_path):
with zipfile.ZipFile(zip_path, &#39;r&#39;) as zipf:
names = zipf.namelist()
config_filename = &#34;config.yaml&#34; if &#34;config.yaml&#34; in names else (&#34;config.json&#34; if &#34;config.json&#34; in names else None)
if config_filename:
with zipf.open(config_filename) as f:
data = yaml.safe_load(f)
connections = data.get(&#34;connections&#34;, {})
# Accurate recursive count
nodes_count = 0
folders_count = 0
# Layer 1
for k, v in connections.items():
if isinstance(v, dict):
if v.get(&#34;type&#34;) == &#34;connection&#34;:
nodes_count += 1
elif v.get(&#34;type&#34;) == &#34;folder&#34;:
folders_count += 1
# Layer 2
for k2, v2 in v.items():
if isinstance(v2, dict):
if v2.get(&#34;type&#34;) == &#34;connection&#34;:
nodes_count += 1
elif v2.get(&#34;type&#34;) == &#34;subfolder&#34;:
folders_count += 1
# Layer 3
for k3, v3 in v2.items():
if isinstance(v3, dict) and v3.get(&#34;type&#34;) == &#34;connection&#34;:
nodes_count += 1
return {
&#34;nodes&#34;: nodes_count,
&#34;folders&#34;: folders_count,
&#34;profiles&#34;: len(data.get(&#34;profiles&#34;, {})),
&#34;has_config&#34;: &#34;config&#34; in data,
&#34;has_key&#34;: &#34;.osk&#34; in names
}
return None</code></pre>
</details>
<div class="desc"><p>Analyze a backup without restoring to provide info for confirmation.</p></div>
</dd>
<dt id="connpy.services.sync_service.SyncService.check_login_status"><code class="name flex">
<span>def <span class="ident">check_login_status</span></span>(<span>self)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def check_login_status(self):
&#34;&#34;&#34;Check if logged in to Google Drive.&#34;&#34;&#34;
if os.path.exists(self.token_file):
creds = Credentials.from_authorized_user_file(self.token_file)
if creds and creds.expired and creds.refresh_token:
try:
creds.refresh(Request())
except RefreshError:
pass
return True if creds.valid else &#34;Invalid&#34;
return False</code></pre>
</details>
<div class="desc"><p>Check if logged in to Google Drive.</p></div>
</dd>
<dt id="connpy.services.sync_service.SyncService.compress_and_upload"><code class="name flex">
<span>def <span class="ident">compress_and_upload</span></span>(<span>self, remote_data=None)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def compress_and_upload(self, remote_data=None):
&#34;&#34;&#34;Compress config and upload to Drive.&#34;&#34;&#34;
timestamp = int(time.time() * 1000)
with tempfile.TemporaryDirectory() as tmp_dir:
zip_path = os.path.join(tmp_dir, f&#34;connpy-backup-{timestamp}.zip&#34;)
with zipfile.ZipFile(zip_path, &#39;w&#39;, zipfile.ZIP_DEFLATED) as zipf:
# If we have remote data, we create a virtual config file
if remote_data:
config_tmp = os.path.join(tmp_dir, &#34;config.yaml&#34;)
with open(config_tmp, &#39;w&#39;) as f:
yaml.dump(remote_data, f, default_flow_style=False)
zipf.write(config_tmp, &#34;config.yaml&#34;)
else:
# Legacy behavior: use local file
zipf.write(self.config.file, os.path.basename(self.config.file))
# Always include the key if it exists
if os.path.exists(self.config.key):
zipf.write(self.config.key, &#34;.osk&#34;)
# Manage retention (max 10 backups)
backups = self.list_backups()
if len(backups) &gt;= 10:
oldest = min(backups, key=lambda x: x[&#39;timestamp&#39;] or &#39;0&#39;)
self.delete_backup(oldest[&#39;id&#39;])
# Upload
return self.upload_file(zip_path, timestamp)</code></pre>
</details>
<div class="desc"><p>Compress config and upload to Drive.</p></div>
</dd>
<dt id="connpy.services.sync_service.SyncService.delete_backup"><code class="name flex">
<span>def <span class="ident">delete_backup</span></span>(<span>self, file_id)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def delete_backup(self, file_id):
&#34;&#34;&#34;Delete a backup from Drive.&#34;&#34;&#34;
creds = self.get_credentials()
if not creds: return False
try:
service = build(&#34;drive&#34;, &#34;v3&#34;, credentials=creds)
service.files().delete(fileId=file_id).execute()
return True
except Exception as e:
printer.error(f&#34;Delete failed: {e}&#34;)
return False</code></pre>
</details>
<div class="desc"><p>Delete a backup from Drive.</p></div>
</dd>
<dt id="connpy.services.sync_service.SyncService.download_file"><code class="name flex">
<span>def <span class="ident">download_file</span></span>(<span>self, file_id, dest)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def download_file(self, file_id, dest):
&#34;&#34;&#34;Internal method to download from Drive.&#34;&#34;&#34;
creds = self.get_credentials()
if not creds: return False
try:
service = build(&#39;drive&#39;, &#39;v3&#39;, credentials=creds)
request = service.files().get_media(fileId=file_id)
with io.FileIO(dest, mode=&#39;wb&#39;) as fh:
downloader = MediaIoBaseDownload(fh, request)
done = False
while not done:
_, done = downloader.next_chunk()
return True
except Exception as e:
printer.error(f&#34;Download failed: {e}&#34;)
return False</code></pre>
</details>
<div class="desc"><p>Internal method to download from Drive.</p></div>
</dd>
<dt id="connpy.services.sync_service.SyncService.get_credentials"><code class="name flex">
<span>def <span class="ident">get_credentials</span></span>(<span>self)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def get_credentials(self):
&#34;&#34;&#34;Get valid credentials, refreshing if necessary.&#34;&#34;&#34;
if os.path.exists(self.token_file):
creds = Credentials.from_authorized_user_file(self.token_file, self.scopes)
else:
return None
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
try:
creds.refresh(Request())
except RefreshError:
return None
else:
return None
return creds</code></pre>
</details>
<div class="desc"><p>Get valid credentials, refreshing if necessary.</p></div>
</dd>
<dt id="connpy.services.sync_service.SyncService.list_backups"><code class="name flex">
<span>def <span class="ident">list_backups</span></span>(<span>self)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def list_backups(self):
&#34;&#34;&#34;List files in Google Drive appDataFolder.&#34;&#34;&#34;
creds = self.get_credentials()
if not creds:
printer.error(&#34;Not logged in to Google Drive.&#34;)
return []
try:
service = build(&#34;drive&#34;, &#34;v3&#34;, credentials=creds)
response = service.files().list(
spaces=&#34;appDataFolder&#34;,
fields=&#34;files(id, name, appProperties)&#34;,
pageSize=10,
).execute()
files_info = []
for file in response.get(&#34;files&#34;, []):
files_info.append({
&#34;name&#34;: file.get(&#34;name&#34;),
&#34;id&#34;: file.get(&#34;id&#34;),
&#34;date&#34;: file.get(&#34;appProperties&#34;, {}).get(&#34;date&#34;),
&#34;timestamp&#34;: file.get(&#34;appProperties&#34;, {}).get(&#34;timestamp&#34;)
})
return files_info
except HttpError as error:
printer.error(f&#34;Google Drive API error: {error}&#34;)
return []</code></pre>
</details>
<div class="desc"><p>List files in Google Drive appDataFolder.</p></div>
</dd>
<dt id="connpy.services.sync_service.SyncService.login"><code class="name flex">
<span>def <span class="ident">login</span></span>(<span>self)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def login(self):
&#34;&#34;&#34;Authenticate with Google Drive.&#34;&#34;&#34;
creds = None
if os.path.exists(self.token_file):
creds = Credentials.from_authorized_user_file(self.token_file, self.scopes)
try:
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_config(self.client_config, self.scopes)
creds = flow.run_local_server(port=0, access_type=&#39;offline&#39;)
with open(self.token_file, &#39;w&#39;) as token:
token.write(creds.to_json())
printer.success(&#34;Logged in successfully.&#34;)
return True
except RefreshError:
if os.path.exists(self.token_file):
os.remove(self.token_file)
printer.warning(&#34;Existing token was invalid and has been removed. Please log in again.&#34;)
return False
except Exception as e:
printer.error(f&#34;Login failed: {e}&#34;)
return False</code></pre>
</details>
<div class="desc"><p>Authenticate with Google Drive.</p></div>
</dd>
<dt id="connpy.services.sync_service.SyncService.logout"><code class="name flex">
<span>def <span class="ident">logout</span></span>(<span>self)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def logout(self):
&#34;&#34;&#34;Remove Google Drive credentials.&#34;&#34;&#34;
if os.path.exists(self.token_file):
os.remove(self.token_file)
printer.success(&#34;Logged out successfully.&#34;)
else:
printer.info(&#34;No credentials file found. Already logged out.&#34;)</code></pre>
</details>
<div class="desc"><p>Remove Google Drive credentials.</p></div>
</dd>
<dt id="connpy.services.sync_service.SyncService.perform_restore"><code class="name flex">
<span>def <span class="ident">perform_restore</span></span>(<span>self, zip_path, restore_config=True, restore_nodes=True, app_instance=None)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def perform_restore(self, zip_path, restore_config=True, restore_nodes=True, app_instance=None):
&#34;&#34;&#34;Execute the actual restoration of files or remote nodes.&#34;&#34;&#34;
try:
with zipfile.ZipFile(zip_path, &#39;r&#39;) as zipf:
names = zipf.namelist()
dest_dir = os.path.dirname(self.config.file)
# We need to read the config content from zip to decide what to do
backup_data = {}
config_filename = &#34;config.yaml&#34; if &#34;config.yaml&#34; in names else (&#34;config.json&#34; if &#34;config.json&#34; in names else None)
if config_filename:
with zipf.open(config_filename) as f:
backup_data = yaml.safe_load(f)
# 1. Restore Key (.osk) - Part of config identity
if restore_config and &#34;.osk&#34; in names:
zipf.extract(&#34;.osk&#34;, os.path.dirname(self.config.key))
# 2. Restore Config (Local Settings)
if restore_config and backup_data:
local_config = self.config.config.copy()
# Capture current connectivity settings to preserve them
current_mode = local_config.get(&#34;service_mode&#34;, &#34;local&#34;)
current_remote = local_config.get(&#34;remote_host&#34;)
if &#34;config&#34; in backup_data:
local_config.update(backup_data[&#34;config&#34;])
# Restore connectivity settings - we don&#39;t want a restore to
# accidentally switch us between local and remote and break connectivity
local_config[&#34;service_mode&#34;] = current_mode
if current_remote:
local_config[&#34;remote_host&#34;] = current_remote
self.config.config = local_config
self.config._saveconfig(self.config.file)
# 3. Restore Nodes and Profiles
if restore_nodes and backup_data:
connections = backup_data.get(&#34;connections&#34;, {})
profiles = backup_data.get(&#34;profiles&#34;, {})
if app_instance and app_instance.services.mode == &#34;remote&#34;:
# Push to Remote via gRPC
app_instance.services.nodes.full_replace(connections, profiles)
else:
# Restore to Local config file
self.config.connections = connections
self.config.profiles = profiles
self.config._saveconfig(self.config.file)
# Clear caches
for f in [self.config.cachefile, self.config.fzf_cachefile]:
if os.path.exists(f): os.remove(f)
return True
except Exception as e:
printer.error(f&#34;Restoration failed: {e}&#34;)
return False</code></pre>
</details>
<div class="desc"><p>Execute the actual restoration of files or remote nodes.</p></div>
</dd>
<dt id="connpy.services.sync_service.SyncService.perform_sync"><code class="name flex">
<span>def <span class="ident">perform_sync</span></span>(<span>self, app_instance)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def perform_sync(self, app_instance):
&#34;&#34;&#34;Background sync logic.&#34;&#34;&#34;
# Always check current config state
sync_enabled = self.config.config.get(&#34;sync&#34;, False)
sync_remote = self.config.config.get(&#34;sync_remote&#34;, False)
if not sync_enabled: return
printer.info(&#34;Triggering auto-sync...&#34;)
if self.check_login_status() != True:
printer.warning(&#34;Auto-sync: Not logged in to Google Drive.&#34;)
return
remote_data = None
if sync_remote and app_instance.services.mode == &#34;remote&#34;:
try:
inventory = app_instance.services.nodes.get_inventory()
# Merge with local settings
local_settings = app_instance.services.config_svc.get_settings()
local_settings.pop(&#34;configfolder&#34;, None)
# Maintain proper config structure: {config: {}, connections: {}, profiles: {}}
remote_data = {
&#34;config&#34;: local_settings,
&#34;connections&#34;: inventory.get(&#34;connections&#34;, {}),
&#34;profiles&#34;: inventory.get(&#34;profiles&#34;, {})
}
except Exception as e:
printer.warning(f&#34;Could not fetch remote inventory for sync: {e}&#34;)
# Run in thread to not block CLI
threading.Thread(
target=self.compress_and_upload,
args=(remote_data,)
).start()</code></pre>
</details>
<div class="desc"><p>Background sync logic.</p></div>
</dd>
<dt id="connpy.services.sync_service.SyncService.restore_backup"><code class="name flex">
<span>def <span class="ident">restore_backup</span></span>(<span>self, file_id=None, restore_config=True, restore_nodes=True, app_instance=None)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def restore_backup(self, file_id=None, restore_config=True, restore_nodes=True, app_instance=None):
&#34;&#34;&#34;Download and analyze a backup for restoration.&#34;&#34;&#34;
backups = self.list_backups()
if not backups:
printer.error(&#34;No backups found.&#34;)
return None
if file_id:
selected = next((f for f in backups if f[&#39;id&#39;] == file_id), None)
if not selected:
printer.error(f&#34;Backup {file_id} not found.&#34;)
return None
else:
selected = max(backups, key=lambda x: x[&#39;timestamp&#39;] or &#39;0&#39;)
with tempfile.TemporaryDirectory() as tmp_dir:
zip_path = os.path.join(tmp_dir, &#39;restore.zip&#39;)
if self.download_file(selected[&#39;id&#39;], zip_path):
return self.perform_restore(zip_path, restore_config, restore_nodes, app_instance)
return False</code></pre>
</details>
<div class="desc"><p>Download and analyze a backup for restoration.</p></div>
</dd>
<dt id="connpy.services.sync_service.SyncService.upload_file"><code class="name flex">
<span>def <span class="ident">upload_file</span></span>(<span>self, file_path, timestamp)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def upload_file(self, file_path, timestamp):
&#34;&#34;&#34;Internal method to upload to Drive.&#34;&#34;&#34;
creds = self.get_credentials()
if not creds: return False
service = build(&#39;drive&#39;, &#39;v3&#39;, credentials=creds)
date_str = datetime.fromtimestamp(timestamp/1000).strftime(&#39;%Y-%m-%d %H:%M:%S&#39;)
file_metadata = {
&#39;name&#39;: os.path.basename(file_path),
&#39;parents&#39;: [&#34;appDataFolder&#34;],
&#39;appProperties&#39;: {
&#39;timestamp&#39;: str(timestamp),
&#39;date&#39;: date_str
}
}
media = MediaFileUpload(file_path)
try:
service.files().create(body=file_metadata, media_body=media, fields=&#39;id&#39;).execute()
printer.success(&#34;Backup uploaded to Google Drive.&#34;)
return True
except Exception as e:
printer.error(f&#34;Upload failed: {e}&#34;)
return False</code></pre>
</details>
<div class="desc"><p>Internal method to upload to Drive.</p></div>
</dd>
</dl>
<h3>Inherited members</h3>
<ul class="hlist">
<li><code><b><a title="connpy.services.base.BaseService" href="base.html#connpy.services.base.BaseService">BaseService</a></b></code>:
<ul class="hlist">
<li><code><a title="connpy.services.base.BaseService.set_reserved_names" href="base.html#connpy.services.base.BaseService.set_reserved_names">set_reserved_names</a></code></li>
</ul>
</li>
</ul>
</dd>
</dl>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="connpy.services" href="index.html">connpy.services</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="connpy.services.sync_service.SyncService" href="#connpy.services.sync_service.SyncService">SyncService</a></code></h4>
<ul class="">
<li><code><a title="connpy.services.sync_service.SyncService.analyze_backup_content" href="#connpy.services.sync_service.SyncService.analyze_backup_content">analyze_backup_content</a></code></li>
<li><code><a title="connpy.services.sync_service.SyncService.check_login_status" href="#connpy.services.sync_service.SyncService.check_login_status">check_login_status</a></code></li>
<li><code><a title="connpy.services.sync_service.SyncService.compress_and_upload" href="#connpy.services.sync_service.SyncService.compress_and_upload">compress_and_upload</a></code></li>
<li><code><a title="connpy.services.sync_service.SyncService.delete_backup" href="#connpy.services.sync_service.SyncService.delete_backup">delete_backup</a></code></li>
<li><code><a title="connpy.services.sync_service.SyncService.download_file" href="#connpy.services.sync_service.SyncService.download_file">download_file</a></code></li>
<li><code><a title="connpy.services.sync_service.SyncService.get_credentials" href="#connpy.services.sync_service.SyncService.get_credentials">get_credentials</a></code></li>
<li><code><a title="connpy.services.sync_service.SyncService.list_backups" href="#connpy.services.sync_service.SyncService.list_backups">list_backups</a></code></li>
<li><code><a title="connpy.services.sync_service.SyncService.login" href="#connpy.services.sync_service.SyncService.login">login</a></code></li>
<li><code><a title="connpy.services.sync_service.SyncService.logout" href="#connpy.services.sync_service.SyncService.logout">logout</a></code></li>
<li><code><a title="connpy.services.sync_service.SyncService.perform_restore" href="#connpy.services.sync_service.SyncService.perform_restore">perform_restore</a></code></li>
<li><code><a title="connpy.services.sync_service.SyncService.perform_sync" href="#connpy.services.sync_service.SyncService.perform_sync">perform_sync</a></code></li>
<li><code><a title="connpy.services.sync_service.SyncService.restore_backup" href="#connpy.services.sync_service.SyncService.restore_backup">restore_backup</a></code></li>
<li><code><a title="connpy.services.sync_service.SyncService.upload_file" href="#connpy.services.sync_service.SyncService.upload_file">upload_file</a></code></li>
</ul>
</li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.5</a>.</p>
</footer>
</body>
</html>
+333
View File
@@ -0,0 +1,333 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.5">
<title>connpy.services.system_service API documentation</title>
<meta name="description" content="">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source > summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible;min-width:max-content}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin:1em 0}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
/* Collapse source docstrings */
setTimeout(() => {
[...document.querySelectorAll('.hljs.language-python > .hljs-string')]
.filter(el => el.innerHTML.length > 200 && ['"""', "'''"].includes(el.innerHTML.substring(0, 3)))
.forEach(el => {
let d = document.createElement('details');
d.classList.add('hljs-string');
d.innerHTML = '<summary>"""</summary>' + el.innerHTML.substring(3);
el.replaceWith(d);
});
}, 100);
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>connpy.services.system_service</code></h1>
</header>
<section id="section-intro">
</section>
<section>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="connpy.services.system_service.SystemService"><code class="flex name class">
<span>class <span class="ident">SystemService</span></span>
<span>(</span><span>config=None)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class SystemService(BaseService):
&#34;&#34;&#34;Business logic for application lifecycle (API, processes).&#34;&#34;&#34;
def start_api(self, port=None):
&#34;&#34;&#34;Start the Connpy REST API.&#34;&#34;&#34;
print(f&#34;DEBUG SystemService: port type={type(port)} value={port}&#34;)
from connpy.api import start_api
try:
start_api(port, config=self.config)
except Exception as e:
raise ConnpyError(f&#34;Failed to start API: {e}&#34;)
def debug_api(self, port=None):
&#34;&#34;&#34;Start the Connpy REST API in debug mode.&#34;&#34;&#34;
from connpy.api import debug_api
try:
debug_api(port, config=self.config)
except Exception as e:
raise ConnpyError(f&#34;Failed to start API in debug mode: {e}&#34;)
def stop_api(self):
&#34;&#34;&#34;Stop the Connpy REST API.&#34;&#34;&#34;
try:
import os
import signal
pids = [&#34;/run/connpy.pid&#34;, &#34;/tmp/connpy.pid&#34;]
stopped = False
for pid_file in pids:
if os.path.exists(pid_file):
try:
with open(pid_file, &#34;r&#34;) as f:
# Read only the first line (PID)
line = f.readline().strip()
if not line:
continue
pid = int(line)
os.kill(pid, signal.SIGTERM)
# Remove the PID file after successful kill
os.remove(pid_file)
stopped = True
except (ValueError, OSError, ProcessLookupError):
# If process is already dead, just remove the stale PID file
try:
os.remove(pid_file)
except OSError:
pass
continue
return stopped
except Exception as e:
raise ConnpyError(f&#34;Failed to stop API: {e}&#34;)
def restart_api(self, port=None):
&#34;&#34;&#34;Restart the Connpy REST API, maintaining the current port if none provided.&#34;&#34;&#34;
if port is None:
status = self.get_api_status()
if status[&#34;running&#34;] and status.get(&#34;port&#34;):
port = status[&#34;port&#34;]
self.stop_api()
import time
time.sleep(1)
self.start_api(port)
def get_api_status(self):
&#34;&#34;&#34;Check if the API is currently running.&#34;&#34;&#34;
import os
pids = [&#34;/run/connpy.pid&#34;, &#34;/tmp/connpy.pid&#34;]
for pid_file in pids:
if os.path.exists(pid_file):
try:
with open(pid_file, &#34;r&#34;) as f:
pid_line = f.readline().strip()
port_line = f.readline().strip()
if not pid_line:
continue
pid = int(pid_line)
port = int(port_line) if port_line else None
# Signal 0 checks for process existence without killing it
os.kill(pid, 0)
return {&#34;running&#34;: True, &#34;pid&#34;: pid, &#34;port&#34;: port, &#34;pid_file&#34;: pid_file}
except (ValueError, OSError, ProcessLookupError):
continue
return {&#34;running&#34;: False}</code></pre>
</details>
<div class="desc"><p>Business logic for application lifecycle (API, processes).</p>
<p>Initialize the service.</p>
<h2 id="args">Args</h2>
<dl>
<dt><strong><code>config</code></strong></dt>
<dd>An instance of configfile (or None to instantiate a new one/use global context).</dd>
</dl></div>
<h3>Ancestors</h3>
<ul class="hlist">
<li><a title="connpy.services.base.BaseService" href="base.html#connpy.services.base.BaseService">BaseService</a></li>
</ul>
<h3>Methods</h3>
<dl>
<dt id="connpy.services.system_service.SystemService.debug_api"><code class="name flex">
<span>def <span class="ident">debug_api</span></span>(<span>self, port=None)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def debug_api(self, port=None):
&#34;&#34;&#34;Start the Connpy REST API in debug mode.&#34;&#34;&#34;
from connpy.api import debug_api
try:
debug_api(port, config=self.config)
except Exception as e:
raise ConnpyError(f&#34;Failed to start API in debug mode: {e}&#34;)</code></pre>
</details>
<div class="desc"><p>Start the Connpy REST API in debug mode.</p></div>
</dd>
<dt id="connpy.services.system_service.SystemService.get_api_status"><code class="name flex">
<span>def <span class="ident">get_api_status</span></span>(<span>self)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def get_api_status(self):
&#34;&#34;&#34;Check if the API is currently running.&#34;&#34;&#34;
import os
pids = [&#34;/run/connpy.pid&#34;, &#34;/tmp/connpy.pid&#34;]
for pid_file in pids:
if os.path.exists(pid_file):
try:
with open(pid_file, &#34;r&#34;) as f:
pid_line = f.readline().strip()
port_line = f.readline().strip()
if not pid_line:
continue
pid = int(pid_line)
port = int(port_line) if port_line else None
# Signal 0 checks for process existence without killing it
os.kill(pid, 0)
return {&#34;running&#34;: True, &#34;pid&#34;: pid, &#34;port&#34;: port, &#34;pid_file&#34;: pid_file}
except (ValueError, OSError, ProcessLookupError):
continue
return {&#34;running&#34;: False}</code></pre>
</details>
<div class="desc"><p>Check if the API is currently running.</p></div>
</dd>
<dt id="connpy.services.system_service.SystemService.restart_api"><code class="name flex">
<span>def <span class="ident">restart_api</span></span>(<span>self, port=None)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def restart_api(self, port=None):
&#34;&#34;&#34;Restart the Connpy REST API, maintaining the current port if none provided.&#34;&#34;&#34;
if port is None:
status = self.get_api_status()
if status[&#34;running&#34;] and status.get(&#34;port&#34;):
port = status[&#34;port&#34;]
self.stop_api()
import time
time.sleep(1)
self.start_api(port)</code></pre>
</details>
<div class="desc"><p>Restart the Connpy REST API, maintaining the current port if none provided.</p></div>
</dd>
<dt id="connpy.services.system_service.SystemService.start_api"><code class="name flex">
<span>def <span class="ident">start_api</span></span>(<span>self, port=None)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def start_api(self, port=None):
&#34;&#34;&#34;Start the Connpy REST API.&#34;&#34;&#34;
print(f&#34;DEBUG SystemService: port type={type(port)} value={port}&#34;)
from connpy.api import start_api
try:
start_api(port, config=self.config)
except Exception as e:
raise ConnpyError(f&#34;Failed to start API: {e}&#34;)</code></pre>
</details>
<div class="desc"><p>Start the Connpy REST API.</p></div>
</dd>
<dt id="connpy.services.system_service.SystemService.stop_api"><code class="name flex">
<span>def <span class="ident">stop_api</span></span>(<span>self)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def stop_api(self):
&#34;&#34;&#34;Stop the Connpy REST API.&#34;&#34;&#34;
try:
import os
import signal
pids = [&#34;/run/connpy.pid&#34;, &#34;/tmp/connpy.pid&#34;]
stopped = False
for pid_file in pids:
if os.path.exists(pid_file):
try:
with open(pid_file, &#34;r&#34;) as f:
# Read only the first line (PID)
line = f.readline().strip()
if not line:
continue
pid = int(line)
os.kill(pid, signal.SIGTERM)
# Remove the PID file after successful kill
os.remove(pid_file)
stopped = True
except (ValueError, OSError, ProcessLookupError):
# If process is already dead, just remove the stale PID file
try:
os.remove(pid_file)
except OSError:
pass
continue
return stopped
except Exception as e:
raise ConnpyError(f&#34;Failed to stop API: {e}&#34;)</code></pre>
</details>
<div class="desc"><p>Stop the Connpy REST API.</p></div>
</dd>
</dl>
<h3>Inherited members</h3>
<ul class="hlist">
<li><code><b><a title="connpy.services.base.BaseService" href="base.html#connpy.services.base.BaseService">BaseService</a></b></code>:
<ul class="hlist">
<li><code><a title="connpy.services.base.BaseService.set_reserved_names" href="base.html#connpy.services.base.BaseService.set_reserved_names">set_reserved_names</a></code></li>
</ul>
</li>
</ul>
</dd>
</dl>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="connpy.services" href="index.html">connpy.services</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="connpy.services.system_service.SystemService" href="#connpy.services.system_service.SystemService">SystemService</a></code></h4>
<ul class="">
<li><code><a title="connpy.services.system_service.SystemService.debug_api" href="#connpy.services.system_service.SystemService.debug_api">debug_api</a></code></li>
<li><code><a title="connpy.services.system_service.SystemService.get_api_status" href="#connpy.services.system_service.SystemService.get_api_status">get_api_status</a></code></li>
<li><code><a title="connpy.services.system_service.SystemService.restart_api" href="#connpy.services.system_service.SystemService.restart_api">restart_api</a></code></li>
<li><code><a title="connpy.services.system_service.SystemService.start_api" href="#connpy.services.system_service.SystemService.start_api">start_api</a></code></li>
<li><code><a title="connpy.services.system_service.SystemService.stop_api" href="#connpy.services.system_service.SystemService.stop_api">stop_api</a></code></li>
</ul>
</li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.5</a>.</p>
</footer>
</body>
</html>
+24 -9
View File
@@ -48,10 +48,6 @@ el.replaceWith(d);
<dd>
<div class="desc"><p>Tests for connpy.ai module.</p></div>
</dd>
<dt><code class="name"><a title="connpy.tests.test_api" href="test_api.html">connpy.tests.test_api</a></code></dt>
<dd>
<div class="desc"><p>Tests for connpy.api module — Flask routes.</p></div>
</dd>
<dt><code class="name"><a title="connpy.tests.test_capture" href="test_capture.html">connpy.tests.test_capture</a></code></dt>
<dd>
<div class="desc"><p>Tests for connpy.core_plugins.capture</p></div>
@@ -64,18 +60,26 @@ el.replaceWith(d);
<dd>
<div class="desc"><p>Tests for connpy.configfile module.</p></div>
</dd>
<dt><code class="name"><a title="connpy.tests.test_context" href="test_context.html">connpy.tests.test_context</a></code></dt>
<dt><code class="name"><a title="connpy.tests.test_connapp" href="test_connapp.html">connpy.tests.test_connapp</a></code></dt>
<dd>
<div class="desc"><p>Tests for connpy.core_plugins.context</p></div>
<div class="desc"></div>
</dd>
<dt><code class="name"><a title="connpy.tests.test_core" href="test_core.html">connpy.tests.test_core</a></code></dt>
<dd>
<div class="desc"><p>Tests for connpy.core module — node and nodes classes.</p></div>
</dd>
<dt><code class="name"><a title="connpy.tests.test_execution_service" href="test_execution_service.html">connpy.tests.test_execution_service</a></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt><code class="name"><a title="connpy.tests.test_hooks" href="test_hooks.html">connpy.tests.test_hooks</a></code></dt>
<dd>
<div class="desc"><p>Tests for connpy.hooks module — MethodHook and ClassHook.</p></div>
</dd>
<dt><code class="name"><a title="connpy.tests.test_node_service" href="test_node_service.html">connpy.tests.test_node_service</a></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt><code class="name"><a title="connpy.tests.test_plugins" href="test_plugins.html">connpy.tests.test_plugins</a></code></dt>
<dd>
<div class="desc"><p>Tests for connpy.plugins module.</p></div>
@@ -84,9 +88,17 @@ el.replaceWith(d);
<dd>
<div class="desc"><p>Tests for connpy.printer module.</p></div>
</dd>
<dt><code class="name"><a title="connpy.tests.test_profile_service" href="test_profile_service.html">connpy.tests.test_profile_service</a></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt><code class="name"><a title="connpy.tests.test_provider" href="test_provider.html">connpy.tests.test_provider</a></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt><code class="name"><a title="connpy.tests.test_sync" href="test_sync.html">connpy.tests.test_sync</a></code></dt>
<dd>
<div class="desc"><p>Tests for connpy.core_plugins.sync</p></div>
<div class="desc"><p>Tests for connpy.services.sync_service</p></div>
</dd>
</dl>
</section>
@@ -111,15 +123,18 @@ el.replaceWith(d);
<ul>
<li><code><a title="connpy.tests.conftest" href="conftest.html">connpy.tests.conftest</a></code></li>
<li><code><a title="connpy.tests.test_ai" href="test_ai.html">connpy.tests.test_ai</a></code></li>
<li><code><a title="connpy.tests.test_api" href="test_api.html">connpy.tests.test_api</a></code></li>
<li><code><a title="connpy.tests.test_capture" href="test_capture.html">connpy.tests.test_capture</a></code></li>
<li><code><a title="connpy.tests.test_completion" href="test_completion.html">connpy.tests.test_completion</a></code></li>
<li><code><a title="connpy.tests.test_configfile" href="test_configfile.html">connpy.tests.test_configfile</a></code></li>
<li><code><a title="connpy.tests.test_context" href="test_context.html">connpy.tests.test_context</a></code></li>
<li><code><a title="connpy.tests.test_connapp" href="test_connapp.html">connpy.tests.test_connapp</a></code></li>
<li><code><a title="connpy.tests.test_core" href="test_core.html">connpy.tests.test_core</a></code></li>
<li><code><a title="connpy.tests.test_execution_service" href="test_execution_service.html">connpy.tests.test_execution_service</a></code></li>
<li><code><a title="connpy.tests.test_hooks" href="test_hooks.html">connpy.tests.test_hooks</a></code></li>
<li><code><a title="connpy.tests.test_node_service" href="test_node_service.html">connpy.tests.test_node_service</a></code></li>
<li><code><a title="connpy.tests.test_plugins" href="test_plugins.html">connpy.tests.test_plugins</a></code></li>
<li><code><a title="connpy.tests.test_printer" href="test_printer.html">connpy.tests.test_printer</a></code></li>
<li><code><a title="connpy.tests.test_profile_service" href="test_profile_service.html">connpy.tests.test_profile_service</a></code></li>
<li><code><a title="connpy.tests.test_provider" href="test_provider.html">connpy.tests.test_provider</a></code></li>
<li><code><a title="connpy.tests.test_sync" href="test_sync.html">connpy.tests.test_sync</a></code></li>
</ul>
</li>
+25 -21
View File
@@ -63,11 +63,13 @@ el.replaceWith(d);
assert myai.engineer_model == &#34;test/test-model&#34;
assert myai.architect_model == &#34;test/test-architect&#34;
def test_init_missing_engineer_key(self, config):
&#34;&#34;&#34;Raises ValueError if engineer key is missing.&#34;&#34;&#34;
def test_ask_missing_engineer_key(self, config):
&#34;&#34;&#34;Raises ValueError if engineer key is missing when asking.&#34;&#34;&#34;
from connpy.ai import ai
with pytest.raises(ValueError, match=&#34;Engineer API key&#34;):
ai(config)
myai = ai(config)
with pytest.raises(ValueError) as exc:
myai.ask(&#34;hello&#34;)
assert &#34;Engineer API key not configured&#34; in str(exc.value)
def test_init_missing_architect_key_warns(self, ai_config, capsys, mock_litellm):
&#34;&#34;&#34;Warns if architect key is missing but doesn&#39;t crash.&#34;&#34;&#34;
@@ -104,6 +106,24 @@ el.replaceWith(d);
<div class="desc"></div>
<h3>Methods</h3>
<dl>
<dt id="connpy.tests.test_ai.TestAIInit.test_ask_missing_engineer_key"><code class="name flex">
<span>def <span class="ident">test_ask_missing_engineer_key</span></span>(<span>self, config)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def test_ask_missing_engineer_key(self, config):
&#34;&#34;&#34;Raises ValueError if engineer key is missing when asking.&#34;&#34;&#34;
from connpy.ai import ai
myai = ai(config)
with pytest.raises(ValueError) as exc:
myai.ask(&#34;hello&#34;)
assert &#34;Engineer API key not configured&#34; in str(exc.value)</code></pre>
</details>
<div class="desc"><p>Raises ValueError if engineer key is missing when asking.</p></div>
</dd>
<dt id="connpy.tests.test_ai.TestAIInit.test_default_models"><code class="name flex">
<span>def <span class="ident">test_default_models</span></span>(<span>self, config)</span>
</code></dt>
@@ -166,22 +186,6 @@ el.replaceWith(d);
</details>
<div class="desc"><p>Warns if architect key is missing but doesn't crash.</p></div>
</dd>
<dt id="connpy.tests.test_ai.TestAIInit.test_init_missing_engineer_key"><code class="name flex">
<span>def <span class="ident">test_init_missing_engineer_key</span></span>(<span>self, config)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def test_init_missing_engineer_key(self, config):
&#34;&#34;&#34;Raises ValueError if engineer key is missing.&#34;&#34;&#34;
from connpy.ai import ai
with pytest.raises(ValueError, match=&#34;Engineer API key&#34;):
ai(config)</code></pre>
</details>
<div class="desc"><p>Raises ValueError if engineer key is missing.</p></div>
</dd>
<dt id="connpy.tests.test_ai.TestAIInit.test_init_with_keys"><code class="name flex">
<span>def <span class="ident">test_init_with_keys</span></span>(<span>self, ai_config, mock_litellm)</span>
</code></dt>
@@ -1615,10 +1619,10 @@ def myai(self, ai_config, mock_litellm):
<li>
<h4><code><a title="connpy.tests.test_ai.TestAIInit" href="#connpy.tests.test_ai.TestAIInit">TestAIInit</a></code></h4>
<ul class="">
<li><code><a title="connpy.tests.test_ai.TestAIInit.test_ask_missing_engineer_key" href="#connpy.tests.test_ai.TestAIInit.test_ask_missing_engineer_key">test_ask_missing_engineer_key</a></code></li>
<li><code><a title="connpy.tests.test_ai.TestAIInit.test_default_models" href="#connpy.tests.test_ai.TestAIInit.test_default_models">test_default_models</a></code></li>
<li><code><a title="connpy.tests.test_ai.TestAIInit.test_init_loads_memory" href="#connpy.tests.test_ai.TestAIInit.test_init_loads_memory">test_init_loads_memory</a></code></li>
<li><code><a title="connpy.tests.test_ai.TestAIInit.test_init_missing_architect_key_warns" href="#connpy.tests.test_ai.TestAIInit.test_init_missing_architect_key_warns">test_init_missing_architect_key_warns</a></code></li>
<li><code><a title="connpy.tests.test_ai.TestAIInit.test_init_missing_engineer_key" href="#connpy.tests.test_ai.TestAIInit.test_init_missing_engineer_key">test_init_missing_engineer_key</a></code></li>
<li><code><a title="connpy.tests.test_ai.TestAIInit.test_init_with_keys" href="#connpy.tests.test_ai.TestAIInit.test_init_with_keys">test_init_with_keys</a></code></li>
</ul>
</li>
+72 -56
View File
@@ -45,6 +45,20 @@ el.replaceWith(d);
<section>
<h2 class="section-title" id="header-functions">Functions</h2>
<dl>
<dt id="connpy.tests.test_capture.RemoteCapture"><code class="name flex">
<span>def <span class="ident">RemoteCapture</span></span>(<span>)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.fixture
def RemoteCapture():
return Entrypoint.get_remote_capture_class()</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_capture.mock_connapp"><code class="name flex">
<span>def <span class="ident">mock_connapp</span></span>(<span>)</span>
</code></dt>
@@ -56,13 +70,14 @@ el.replaceWith(d);
<pre><code class="python">@pytest.fixture
def mock_connapp():
app = MagicMock()
app.nodes_list = [&#34;test_node&#34;]
app.config.getitem.return_value = {&#34;host&#34;: &#34;127.0.0.1&#34;, &#34;protocol&#34;: &#34;ssh&#34;}
app.services.nodes.list_nodes.return_value = [&#34;test_node&#34;]
app.services.nodes.get_node_details.return_value = {&#34;host&#34;: &#34;127.0.0.1&#34;, &#34;protocol&#34;: &#34;ssh&#34;}
app.services.config_svc.get_settings().get.return_value = &#34;/fake/ws&#34;
mock_node = MagicMock()
mock_node.protocol = &#34;ssh&#34;
mock_node.unique = &#34;test_node&#34;
app.node.return_value = mock_node
app.config.config = {&#34;wireshark_path&#34;: &#34;/fake/ws&#34;}
return app</code></pre>
</details>
<div class="desc"></div>
@@ -81,34 +96,54 @@ def mock_connapp():
<span>Expand source code</span>
</summary>
<pre><code class="python">class TestRemoteCapture:
def test_init_node_not_found(self, mock_connapp):
# Attempt to capture a node not in nodes_list
mock_connapp.nodes_list = [&#34;other_node&#34;]
def test_init_node_not_found(self, mock_connapp, RemoteCapture):
# Attempt to capture a node not in inventory
mock_connapp.services.nodes.list_nodes.return_value = []
with pytest.raises(SystemExit) as exc:
RemoteCapture(mock_connapp, &#34;test_node&#34;, &#34;eth0&#34;)
assert exc.value.code == 2
def test_init_success(self, mock_connapp):
def test_init_success(self, mock_connapp, RemoteCapture):
rc = RemoteCapture(mock_connapp, &#34;test_node&#34;, &#34;eth0&#34;)
assert rc.node_name == &#34;test_node&#34;
assert rc.interface == &#34;eth0&#34;
assert rc.wireshark_path == &#34;/fake/ws&#34;
@patch(&#34;connpy.core_plugins.capture.socket&#34;)
def test_is_port_in_use(self, mock_socket, mock_connapp):
def test_is_port_in_use(self, mock_connapp, RemoteCapture):
rc = RemoteCapture(mock_connapp, &#34;test_node&#34;, &#34;eth0&#34;)
mock_sock_instance = MagicMock()
mock_socket.socket.return_value.__enter__.return_value = mock_sock_instance
mock_sock_instance.connect_ex.return_value = 0
assert rc._is_port_in_use(8080) is True
mock_sock_instance.connect_ex.return_value = 1
assert rc._is_port_in_use(8080) is False
with patch(&#34;socket.socket&#34;) as mock_socket:
mock_sock_instance = MagicMock()
mock_socket.return_value.__enter__.return_value = mock_sock_instance
mock_sock_instance.connect_ex.return_value = 0
assert rc._is_port_in_use(8080) is True
mock_sock_instance.connect_ex.return_value = 1
assert rc._is_port_in_use(8080) is False
@patch.object(RemoteCapture, &#34;_is_port_in_use&#34;)
def test_find_free_port(self, mock_is_in_use, mock_connapp):
def test_find_free_port(self, mock_connapp, RemoteCapture):
rc = RemoteCapture(mock_connapp, &#34;test_node&#34;, &#34;eth0&#34;)
with patch.object(RemoteCapture, &#34;_is_port_in_use&#34;) as mock_is_in_use:
# First 2 ports in use, 3rd is free
mock_is_in_use.side_effect = [True, True, False]
port = rc._find_free_port(20000, 30000)
assert 20000 &lt;= port &lt;= 30000
assert mock_is_in_use.call_count == 3</code></pre>
</details>
<div class="desc"></div>
<h3>Methods</h3>
<dl>
<dt id="connpy.tests.test_capture.TestRemoteCapture.test_find_free_port"><code class="name flex">
<span>def <span class="ident">test_find_free_port</span></span>(<span>self, mock_connapp, RemoteCapture)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def test_find_free_port(self, mock_connapp, RemoteCapture):
rc = RemoteCapture(mock_connapp, &#34;test_node&#34;, &#34;eth0&#34;)
with patch.object(RemoteCapture, &#34;_is_port_in_use&#34;) as mock_is_in_use:
# First 2 ports in use, 3rd is free
mock_is_in_use.side_effect = [True, True, False]
port = rc._find_free_port(20000, 30000)
@@ -116,38 +151,18 @@ def mock_connapp():
assert mock_is_in_use.call_count == 3</code></pre>
</details>
<div class="desc"></div>
<h3>Methods</h3>
<dl>
<dt id="connpy.tests.test_capture.TestRemoteCapture.test_find_free_port"><code class="name flex">
<span>def <span class="ident">test_find_free_port</span></span>(<span>self, mock_is_in_use, mock_connapp)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@patch.object(RemoteCapture, &#34;_is_port_in_use&#34;)
def test_find_free_port(self, mock_is_in_use, mock_connapp):
rc = RemoteCapture(mock_connapp, &#34;test_node&#34;, &#34;eth0&#34;)
# First 2 ports in use, 3rd is free
mock_is_in_use.side_effect = [True, True, False]
port = rc._find_free_port(20000, 30000)
assert 20000 &lt;= port &lt;= 30000
assert mock_is_in_use.call_count == 3</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_capture.TestRemoteCapture.test_init_node_not_found"><code class="name flex">
<span>def <span class="ident">test_init_node_not_found</span></span>(<span>self, mock_connapp)</span>
<span>def <span class="ident">test_init_node_not_found</span></span>(<span>self, mock_connapp, RemoteCapture)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def test_init_node_not_found(self, mock_connapp):
# Attempt to capture a node not in nodes_list
mock_connapp.nodes_list = [&#34;other_node&#34;]
<pre><code class="python">def test_init_node_not_found(self, mock_connapp, RemoteCapture):
# Attempt to capture a node not in inventory
mock_connapp.services.nodes.list_nodes.return_value = []
with pytest.raises(SystemExit) as exc:
RemoteCapture(mock_connapp, &#34;test_node&#34;, &#34;eth0&#34;)
assert exc.value.code == 2</code></pre>
@@ -155,14 +170,14 @@ def test_find_free_port(self, mock_is_in_use, mock_connapp):
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_capture.TestRemoteCapture.test_init_success"><code class="name flex">
<span>def <span class="ident">test_init_success</span></span>(<span>self, mock_connapp)</span>
<span>def <span class="ident">test_init_success</span></span>(<span>self, mock_connapp, RemoteCapture)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def test_init_success(self, mock_connapp):
<pre><code class="python">def test_init_success(self, mock_connapp, RemoteCapture):
rc = RemoteCapture(mock_connapp, &#34;test_node&#34;, &#34;eth0&#34;)
assert rc.node_name == &#34;test_node&#34;
assert rc.interface == &#34;eth0&#34;
@@ -171,24 +186,24 @@ def test_find_free_port(self, mock_is_in_use, mock_connapp):
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_capture.TestRemoteCapture.test_is_port_in_use"><code class="name flex">
<span>def <span class="ident">test_is_port_in_use</span></span>(<span>self, mock_socket, mock_connapp)</span>
<span>def <span class="ident">test_is_port_in_use</span></span>(<span>self, mock_connapp, RemoteCapture)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@patch(&#34;connpy.core_plugins.capture.socket&#34;)
def test_is_port_in_use(self, mock_socket, mock_connapp):
<pre><code class="python">def test_is_port_in_use(self, mock_connapp, RemoteCapture):
rc = RemoteCapture(mock_connapp, &#34;test_node&#34;, &#34;eth0&#34;)
mock_sock_instance = MagicMock()
mock_socket.socket.return_value.__enter__.return_value = mock_sock_instance
mock_sock_instance.connect_ex.return_value = 0
assert rc._is_port_in_use(8080) is True
mock_sock_instance.connect_ex.return_value = 1
assert rc._is_port_in_use(8080) is False</code></pre>
with patch(&#34;socket.socket&#34;) as mock_socket:
mock_sock_instance = MagicMock()
mock_socket.return_value.__enter__.return_value = mock_sock_instance
mock_sock_instance.connect_ex.return_value = 0
assert rc._is_port_in_use(8080) is True
mock_sock_instance.connect_ex.return_value = 1
assert rc._is_port_in_use(8080) is False</code></pre>
</details>
<div class="desc"></div>
</dd>
@@ -209,6 +224,7 @@ def test_is_port_in_use(self, mock_socket, mock_connapp):
</li>
<li><h3><a href="#header-functions">Functions</a></h3>
<ul class="">
<li><code><a title="connpy.tests.test_capture.RemoteCapture" href="#connpy.tests.test_capture.RemoteCapture">RemoteCapture</a></code></li>
<li><code><a title="connpy.tests.test_capture.mock_connapp" href="#connpy.tests.test_capture.mock_connapp">mock_connapp</a></code></li>
</ul>
</li>
+6 -182
View File
@@ -64,7 +64,7 @@ el.replaceWith(d);
subdir = tmp_path / &#34;subdir&#34;
subdir.mkdir()
result = _getcwd([&#34;run&#34;, &#34;run&#34;], &#34;run&#34;)
result = get_cwd([&#34;run&#34;, &#34;run&#34;])
# Should list files
assert any(&#34;file1.txt&#34; in r for r in result)
assert any(&#34;subdir/&#34; in r for r in result)
@@ -75,7 +75,7 @@ el.replaceWith(d);
(tmp_path / &#34;script.yaml&#34;).touch()
(tmp_path / &#34;script2.yaml&#34;).touch()
result = _getcwd([&#34;run&#34;, &#34;script&#34;], &#34;run&#34;)
result = get_cwd([&#34;run&#34;, &#34;script&#34;])
assert any(&#34;script&#34; in r for r in result)
def test_folder_only(self, tmp_path, monkeypatch):
@@ -85,7 +85,7 @@ el.replaceWith(d);
subdir = tmp_path / &#34;mydir&#34;
subdir.mkdir()
result = _getcwd([&#34;export&#34;, &#34;export&#34;], &#34;export&#34;, folderonly=True)
result = get_cwd([&#34;export&#34;, &#34;export&#34;], folderonly=True)
files_in_result = [r for r in result if &#34;file.txt&#34; in r]
assert len(files_in_result) == 0
dirs_in_result = [r for r in result if &#34;mydir&#34; in r]
@@ -110,7 +110,7 @@ el.replaceWith(d);
subdir = tmp_path / &#34;subdir&#34;
subdir.mkdir()
result = _getcwd([&#34;run&#34;, &#34;run&#34;], &#34;run&#34;)
result = get_cwd([&#34;run&#34;, &#34;run&#34;])
# Should list files
assert any(&#34;file1.txt&#34; in r for r in result)
assert any(&#34;subdir/&#34; in r for r in result)</code></pre>
@@ -132,7 +132,7 @@ el.replaceWith(d);
subdir = tmp_path / &#34;mydir&#34;
subdir.mkdir()
result = _getcwd([&#34;export&#34;, &#34;export&#34;], &#34;export&#34;, folderonly=True)
result = get_cwd([&#34;export&#34;, &#34;export&#34;], folderonly=True)
files_in_result = [r for r in result if &#34;file.txt&#34; in r]
assert len(files_in_result) == 0
dirs_in_result = [r for r in result if &#34;mydir&#34; in r]
@@ -154,179 +154,13 @@ el.replaceWith(d);
(tmp_path / &#34;script.yaml&#34;).touch()
(tmp_path / &#34;script2.yaml&#34;).touch()
result = _getcwd([&#34;run&#34;, &#34;script&#34;], &#34;run&#34;)
result = get_cwd([&#34;run&#34;, &#34;script&#34;])
assert any(&#34;script&#34; in r for r in result)</code></pre>
</details>
<div class="desc"><p>Lists files matching a partial path.</p></div>
</dd>
</dl>
</dd>
<dt id="connpy.tests.test_completion.TestGetPlugins"><code class="flex name class">
<span>class <span class="ident">TestGetPlugins</span></span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class TestGetPlugins:
def test_get_plugins_disable(self, tmp_path):
&#34;&#34;&#34;--disable returns enabled plugins.&#34;&#34;&#34;
plugin_dir = tmp_path / &#34;plugins&#34;
plugin_dir.mkdir()
(plugin_dir / &#34;active.py&#34;).touch()
(plugin_dir / &#34;disabled.py.bkp&#34;).touch()
result = _get_plugins(&#34;--disable&#34;, str(tmp_path))
assert &#34;active&#34; in result
assert &#34;disabled&#34; not in result
def test_get_plugins_enable(self, tmp_path):
&#34;&#34;&#34;--enable returns disabled plugins.&#34;&#34;&#34;
plugin_dir = tmp_path / &#34;plugins&#34;
plugin_dir.mkdir()
(plugin_dir / &#34;active.py&#34;).touch()
(plugin_dir / &#34;disabled.py.bkp&#34;).touch()
result = _get_plugins(&#34;--enable&#34;, str(tmp_path))
assert &#34;disabled&#34; in result
assert &#34;active&#34; not in result
def test_get_plugins_del(self, tmp_path):
&#34;&#34;&#34;--del returns all plugins.&#34;&#34;&#34;
plugin_dir = tmp_path / &#34;plugins&#34;
plugin_dir.mkdir()
(plugin_dir / &#34;active.py&#34;).touch()
(plugin_dir / &#34;disabled.py.bkp&#34;).touch()
result = _get_plugins(&#34;--del&#34;, str(tmp_path))
assert &#34;active&#34; in result
assert &#34;disabled&#34; in result
def test_get_plugins_all(self, tmp_path):
&#34;&#34;&#34;&#39;all&#39; returns dict with paths.&#34;&#34;&#34;
plugin_dir = tmp_path / &#34;plugins&#34;
plugin_dir.mkdir()
(plugin_dir / &#34;myplugin.py&#34;).touch()
result = _get_plugins(&#34;all&#34;, str(tmp_path))
assert isinstance(result, dict)
assert &#34;myplugin&#34; in result
def test_get_plugins_empty_dir(self, tmp_path):
&#34;&#34;&#34;Empty plugins directory returns empty list.&#34;&#34;&#34;
plugin_dir = tmp_path / &#34;plugins&#34;
plugin_dir.mkdir()
result = _get_plugins(&#34;--disable&#34;, str(tmp_path))
assert result == []</code></pre>
</details>
<div class="desc"></div>
<h3>Methods</h3>
<dl>
<dt id="connpy.tests.test_completion.TestGetPlugins.test_get_plugins_all"><code class="name flex">
<span>def <span class="ident">test_get_plugins_all</span></span>(<span>self, tmp_path)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def test_get_plugins_all(self, tmp_path):
&#34;&#34;&#34;&#39;all&#39; returns dict with paths.&#34;&#34;&#34;
plugin_dir = tmp_path / &#34;plugins&#34;
plugin_dir.mkdir()
(plugin_dir / &#34;myplugin.py&#34;).touch()
result = _get_plugins(&#34;all&#34;, str(tmp_path))
assert isinstance(result, dict)
assert &#34;myplugin&#34; in result</code></pre>
</details>
<div class="desc"><p>'all' returns dict with paths.</p></div>
</dd>
<dt id="connpy.tests.test_completion.TestGetPlugins.test_get_plugins_del"><code class="name flex">
<span>def <span class="ident">test_get_plugins_del</span></span>(<span>self, tmp_path)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def test_get_plugins_del(self, tmp_path):
&#34;&#34;&#34;--del returns all plugins.&#34;&#34;&#34;
plugin_dir = tmp_path / &#34;plugins&#34;
plugin_dir.mkdir()
(plugin_dir / &#34;active.py&#34;).touch()
(plugin_dir / &#34;disabled.py.bkp&#34;).touch()
result = _get_plugins(&#34;--del&#34;, str(tmp_path))
assert &#34;active&#34; in result
assert &#34;disabled&#34; in result</code></pre>
</details>
<div class="desc"><p>&ndash;del returns all plugins.</p></div>
</dd>
<dt id="connpy.tests.test_completion.TestGetPlugins.test_get_plugins_disable"><code class="name flex">
<span>def <span class="ident">test_get_plugins_disable</span></span>(<span>self, tmp_path)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def test_get_plugins_disable(self, tmp_path):
&#34;&#34;&#34;--disable returns enabled plugins.&#34;&#34;&#34;
plugin_dir = tmp_path / &#34;plugins&#34;
plugin_dir.mkdir()
(plugin_dir / &#34;active.py&#34;).touch()
(plugin_dir / &#34;disabled.py.bkp&#34;).touch()
result = _get_plugins(&#34;--disable&#34;, str(tmp_path))
assert &#34;active&#34; in result
assert &#34;disabled&#34; not in result</code></pre>
</details>
<div class="desc"><p>&ndash;disable returns enabled plugins.</p></div>
</dd>
<dt id="connpy.tests.test_completion.TestGetPlugins.test_get_plugins_empty_dir"><code class="name flex">
<span>def <span class="ident">test_get_plugins_empty_dir</span></span>(<span>self, tmp_path)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def test_get_plugins_empty_dir(self, tmp_path):
&#34;&#34;&#34;Empty plugins directory returns empty list.&#34;&#34;&#34;
plugin_dir = tmp_path / &#34;plugins&#34;
plugin_dir.mkdir()
result = _get_plugins(&#34;--disable&#34;, str(tmp_path))
assert result == []</code></pre>
</details>
<div class="desc"><p>Empty plugins directory returns empty list.</p></div>
</dd>
<dt id="connpy.tests.test_completion.TestGetPlugins.test_get_plugins_enable"><code class="name flex">
<span>def <span class="ident">test_get_plugins_enable</span></span>(<span>self, tmp_path)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def test_get_plugins_enable(self, tmp_path):
&#34;&#34;&#34;--enable returns disabled plugins.&#34;&#34;&#34;
plugin_dir = tmp_path / &#34;plugins&#34;
plugin_dir.mkdir()
(plugin_dir / &#34;active.py&#34;).touch()
(plugin_dir / &#34;disabled.py.bkp&#34;).touch()
result = _get_plugins(&#34;--enable&#34;, str(tmp_path))
assert &#34;disabled&#34; in result
assert &#34;active&#34; not in result</code></pre>
</details>
<div class="desc"><p>&ndash;enable returns disabled plugins.</p></div>
</dd>
</dl>
</dd>
<dt id="connpy.tests.test_completion.TestLoadTxtCache"><code class="flex name class">
<span>class <span class="ident">TestLoadTxtCache</span></span>
</code></dt>
@@ -411,16 +245,6 @@ el.replaceWith(d);
</ul>
</li>
<li>
<h4><code><a title="connpy.tests.test_completion.TestGetPlugins" href="#connpy.tests.test_completion.TestGetPlugins">TestGetPlugins</a></code></h4>
<ul class="">
<li><code><a title="connpy.tests.test_completion.TestGetPlugins.test_get_plugins_all" href="#connpy.tests.test_completion.TestGetPlugins.test_get_plugins_all">test_get_plugins_all</a></code></li>
<li><code><a title="connpy.tests.test_completion.TestGetPlugins.test_get_plugins_del" href="#connpy.tests.test_completion.TestGetPlugins.test_get_plugins_del">test_get_plugins_del</a></code></li>
<li><code><a title="connpy.tests.test_completion.TestGetPlugins.test_get_plugins_disable" href="#connpy.tests.test_completion.TestGetPlugins.test_get_plugins_disable">test_get_plugins_disable</a></code></li>
<li><code><a title="connpy.tests.test_completion.TestGetPlugins.test_get_plugins_empty_dir" href="#connpy.tests.test_completion.TestGetPlugins.test_get_plugins_empty_dir">test_get_plugins_empty_dir</a></code></li>
<li><code><a title="connpy.tests.test_completion.TestGetPlugins.test_get_plugins_enable" href="#connpy.tests.test_completion.TestGetPlugins.test_get_plugins_enable">test_get_plugins_enable</a></code></li>
</ul>
</li>
<li>
<h4><code><a title="connpy.tests.test_completion.TestLoadTxtCache" href="#connpy.tests.test_completion.TestLoadTxtCache">TestLoadTxtCache</a></code></h4>
<ul class="">
<li><code><a title="connpy.tests.test_completion.TestLoadTxtCache.test_load_existing_cache" href="#connpy.tests.test_completion.TestLoadTxtCache.test_load_existing_cache">test_load_existing_cache</a></code></li>
+5 -3
View File
@@ -1140,8 +1140,9 @@ el.replaceWith(d);
assert &#34;server1@office&#34; not in nodes
def test_getallnodes_filter_invalid_type(self, populated_config):
with pytest.raises(ValueError):
with pytest.raises(SystemExit) as exc:
populated_config._getallnodes(123)
assert exc.value.code == 1
def test_getallfolders(self, populated_config):
folders = populated_config._getallfolders()
@@ -1236,8 +1237,9 @@ el.replaceWith(d);
<span>Expand source code</span>
</summary>
<pre><code class="python">def test_getallnodes_filter_invalid_type(self, populated_config):
with pytest.raises(ValueError):
populated_config._getallnodes(123)</code></pre>
with pytest.raises(SystemExit) as exc:
populated_config._getallnodes(123)
assert exc.value.code == 1</code></pre>
</details>
<div class="desc"></div>
</dd>
+705
View File
@@ -0,0 +1,705 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.5">
<title>connpy.tests.test_connapp API documentation</title>
<meta name="description" content="">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source > summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible;min-width:max-content}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin:1em 0}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
/* Collapse source docstrings */
setTimeout(() => {
[...document.querySelectorAll('.hljs.language-python > .hljs-string')]
.filter(el => el.innerHTML.length > 200 && ['"""', "'''"].includes(el.innerHTML.substring(0, 3)))
.forEach(el => {
let d = document.createElement('details');
d.classList.add('hljs-string');
d.innerHTML = '<summary>"""</summary>' + el.innerHTML.substring(3);
el.replaceWith(d);
});
}, 100);
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>connpy.tests.test_connapp</code></h1>
</header>
<section id="section-intro">
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-functions">Functions</h2>
<dl>
<dt id="connpy.tests.test_connapp.app"><code class="name flex">
<span>def <span class="ident">app</span></span>(<span>populated_config)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.fixture
def app(populated_config):
&#34;&#34;&#34;Returns an instance of connapp initialized with the mock config.&#34;&#34;&#34;
return connapp(populated_config)</code></pre>
</details>
<div class="desc"><p>Returns an instance of connapp initialized with the mock config.</p></div>
</dd>
<dt id="connpy.tests.test_connapp.test_ai"><code class="name flex">
<span>def <span class="ident">test_ai</span></span>(<span>mock_status, mock_ask, app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@patch(&#34;connpy.services.ai_service.AIService.ask&#34;)
@patch(&#34;connpy.connapp.console.status&#34;)
def test_ai(mock_status, mock_ask, app):
mock_ask.return_value = {&#34;response&#34;: &#34;AI output&#34;, &#34;usage&#34;: {&#34;total&#34;: 10, &#34;input&#34;: 5, &#34;output&#34;: 5}}
app.start([&#34;ai&#34;, &#34;--engineer-api-key&#34;, &#34;testkey&#34;, &#34;how are you&#34;])
mock_ask.assert_called_once()</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_connapp.test_ai_list"><code class="name flex">
<span>def <span class="ident">test_ai_list</span></span>(<span>mock_list_sessions, app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@patch(&#34;connpy.services.ai_service.AIService.list_sessions&#34;)
def test_ai_list(mock_list_sessions, app):
mock_list_sessions.return_value = [{&#34;id&#34;: &#34;1&#34;, &#34;title&#34;: &#34;t&#34;, &#34;created_at&#34;: &#34;now&#34;, &#34;model&#34;: &#34;m&#34;}]
app.start([&#34;ai&#34;, &#34;--list&#34;])
mock_list_sessions.assert_called_once()</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_connapp.test_api_debug"><code class="name flex">
<span>def <span class="ident">test_api_debug</span></span>(<span>mock_status, app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@patch(&#34;connpy.services.system_service.SystemService.get_api_status&#34;)
def test_api_debug(mock_status, app):
mock_status.return_value = {&#34;running&#34;: False}
app.services.system.debug_api = MagicMock()
app.start([&#34;api&#34;, &#34;-d&#34;, &#34;8080&#34;])
app.services.system.debug_api.assert_called_once_with(port=8080)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_connapp.test_api_start"><code class="name flex">
<span>def <span class="ident">test_api_start</span></span>(<span>mock_status, app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@patch(&#34;connpy.services.system_service.SystemService.get_api_status&#34;)
def test_api_start(mock_status, app):
mock_status.return_value = {&#34;running&#34;: False}
app.services.system.start_api = MagicMock()
app.start([&#34;api&#34;, &#34;-s&#34;, &#34;8080&#34;])
app.services.system.start_api.assert_called_once_with(port=8080)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_connapp.test_api_stop"><code class="name flex">
<span>def <span class="ident">test_api_stop</span></span>(<span>mock_status, app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@patch(&#34;connpy.services.system_service.SystemService.get_api_status&#34;)
def test_api_stop(mock_status, app):
mock_status.return_value = {&#34;running&#34;: True, &#34;pid&#34;: &#34;1234&#34;}
app.services.system.stop_api = MagicMock(return_value=True)
app.start([&#34;api&#34;, &#34;-x&#34;])
app.services.system.stop_api.assert_called_once()</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_connapp.test_bulk"><code class="name flex">
<span>def <span class="ident">test_bulk</span></span>(<span>mock_bulk_add, mock_q_bulk, app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@patch(&#34;connpy.cli.forms.Forms.questions_bulk&#34;)
@patch(&#34;connpy.services.node_service.NodeService.bulk_add&#34;)
def test_bulk(mock_bulk_add, mock_q_bulk, app):
mock_q_bulk.return_value = {&#34;ids&#34;: &#34;node1&#34;, &#34;host&#34;: &#34;host1&#34;, &#34;location&#34;: &#34;&#34;}
mock_bulk_add.return_value = 1
app.start([&#34;bulk&#34;])
mock_bulk_add.assert_called_once()</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_connapp.test_config"><code class="name flex">
<span>def <span class="ident">test_config</span></span>(<span>mock_update_setting, app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@patch(&#34;connpy.services.config_service.ConfigService.update_setting&#34;)
def test_config(mock_update_setting, app):
app.start([&#34;config&#34;, &#34;--allow-uppercase&#34;, &#34;true&#34;])
mock_update_setting.assert_called_with(&#34;case&#34;, True)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_connapp.test_config_folder"><code class="name flex">
<span>def <span class="ident">test_config_folder</span></span>(<span>mock_set_config_folder, app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@patch(&#34;connpy.services.config_service.ConfigService.set_config_folder&#34;)
def test_config_folder(mock_set_config_folder, app):
app.start([&#34;config&#34;, &#34;--configfolder&#34;, &#34;/new/path&#34;])
mock_set_config_folder.assert_called_once_with(&#34;/new/path&#34;)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_connapp.test_config_various"><code class="name flex">
<span>def <span class="ident">test_config_various</span></span>(<span>mock_update_setting, app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@patch(&#34;connpy.services.config_service.ConfigService.update_setting&#34;)
def test_config_various(mock_update_setting, app):
app.start([&#34;config&#34;, &#34;--fzf&#34;, &#34;true&#34;])
mock_update_setting.assert_called_with(&#34;fzf&#34;, True)
app.start([&#34;config&#34;, &#34;--keepalive&#34;, &#34;60&#34;])
mock_update_setting.assert_called_with(&#34;idletime&#34;, 60)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_connapp.test_connapp_init"><code class="name flex">
<span>def <span class="ident">test_connapp_init</span></span>(<span>app, populated_config)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def test_connapp_init(app, populated_config):
&#34;&#34;&#34;Test that connapp initializes correctly with config.&#34;&#34;&#34;
assert app.config == populated_config
assert app.case == populated_config.config.get(&#34;case&#34;, False)</code></pre>
</details>
<div class="desc"><p>Test that connapp initializes correctly with config.</p></div>
</dd>
<dt id="connpy.tests.test_connapp.test_copy"><code class="name flex">
<span>def <span class="ident">test_copy</span></span>(<span>mock_move_node, app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@patch(&#34;connpy.services.node_service.NodeService.move_node&#34;)
def test_copy(mock_move_node, app):
app.start([&#34;copy&#34;, &#34;src_node&#34;, &#34;dst_node&#34;])
mock_move_node.assert_called_once_with(&#34;src_node&#34;, &#34;dst_node&#34;, copy=True)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_connapp.test_export"><code class="name flex">
<span>def <span class="ident">test_export</span></span>(<span>mock_export, app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@patch(&#34;connpy.services.import_export_service.ImportExportService.export_to_file&#34;)
def test_export(mock_export, app):
with pytest.raises(SystemExit):
app.start([&#34;export&#34;, &#34;file.yml&#34;, &#34;@folder1&#34;])
mock_export.assert_called_once_with(&#34;file.yml&#34;, folders=[&#34;@folder1&#34;])</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_connapp.test_import"><code class="name flex">
<span>def <span class="ident">test_import</span></span>(<span>mock_import, mock_prompt, mock_exists, app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@patch(&#34;os.path.exists&#34;)
@patch(&#34;inquirer.prompt&#34;)
@patch(&#34;connpy.services.import_export_service.ImportExportService.import_from_file&#34;)
def test_import(mock_import, mock_prompt, mock_exists, app):
mock_exists.return_value = True
mock_prompt.return_value = {&#34;import&#34;: True}
app.start([&#34;import&#34;, &#34;file.yml&#34;])
mock_import.assert_called_once_with(&#34;file.yml&#34;)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_connapp.test_list_folders"><code class="name flex">
<span>def <span class="ident">test_list_folders</span></span>(<span>mock_list_folders, app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@patch(&#34;connpy.services.node_service.NodeService.list_folders&#34;)
def test_list_folders(mock_list_folders, app):
mock_list_folders.return_value = [&#34;folder1&#34;]
app.start([&#34;list&#34;, &#34;folders&#34;])
# Called during init and during the list command
assert mock_list_folders.call_count &gt;= 2</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_connapp.test_move"><code class="name flex">
<span>def <span class="ident">test_move</span></span>(<span>mock_move_node, app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@patch(&#34;connpy.services.node_service.NodeService.move_node&#34;)
def test_move(mock_move_node, app):
app.start([&#34;move&#34;, &#34;src_node&#34;, &#34;dst_node&#34;])
mock_move_node.assert_called_once_with(&#34;src_node&#34;, &#34;dst_node&#34;, copy=False)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_connapp.test_node_add"><code class="name flex">
<span>def <span class="ident">test_node_add</span></span>(<span>mock_func_node, app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@patch(&#34;connpy.cli.node_handler.NodeHandler.dispatch&#34;)
def test_node_add(mock_func_node, app):
&#34;&#34;&#34;Test that &#39;node -a&#39; command correctly parses.&#34;&#34;&#34;
app.start([&#34;node&#34;, &#34;-a&#34;, &#34;new_router&#34;])
mock_func_node.assert_called_once()
args = mock_func_node.call_args[0][0]
assert args.data == &#34;new_router&#34;
assert args.action == &#34;add&#34;</code></pre>
</details>
<div class="desc"><p>Test that 'node -a' command correctly parses.</p></div>
</dd>
<dt id="connpy.tests.test_connapp.test_node_default"><code class="name flex">
<span>def <span class="ident">test_node_default</span></span>(<span>mock_func_node, app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@patch(&#34;connpy.cli.node_handler.NodeHandler.dispatch&#34;)
def test_node_default(mock_func_node, app):
&#34;&#34;&#34;Test that default &#39;node&#39; command correctly parses and calls _func_node.&#34;&#34;&#34;
app.start([&#34;node&#34;, &#34;router1&#34;])
mock_func_node.assert_called_once()
args = mock_func_node.call_args[0][0]
assert args.data == &#34;router1&#34;
assert args.action == &#34;connect&#34;</code></pre>
</details>
<div class="desc"><p>Test that default 'node' command correctly parses and calls _func_node.</p></div>
</dd>
<dt id="connpy.tests.test_connapp.test_node_del"><code class="name flex">
<span>def <span class="ident">test_node_del</span></span>(<span>mock_prompt, mock_delete_node, mock_list_nodes, app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@patch(&#34;connpy.services.node_service.NodeService.list_nodes&#34;)
@patch(&#34;connpy.services.node_service.NodeService.delete_node&#34;)
@patch(&#34;inquirer.prompt&#34;)
def test_node_del(mock_prompt, mock_delete_node, mock_list_nodes, app):
mock_list_nodes.return_value = [&#34;router1&#34;]
mock_prompt.return_value = {&#34;delete&#34;: True}
app.start([&#34;node&#34;, &#34;-r&#34;, &#34;router1&#34;])
mock_delete_node.assert_called_once_with(&#34;router1&#34;, is_folder=False)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_connapp.test_node_list"><code class="name flex">
<span>def <span class="ident">test_node_list</span></span>(<span>mock_list_nodes, app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@patch(&#34;connpy.services.node_service.NodeService.list_nodes&#34;)
def test_node_list(mock_list_nodes, app):
&#34;&#34;&#34;Test &#39;list nodes&#39; invokes node service.&#34;&#34;&#34;
mock_list_nodes.return_value = [&#34;router1&#34;, &#34;server1&#34;]
app.start([&#34;list&#34;, &#34;nodes&#34;])
# Should be called during init and during the list command
assert mock_list_nodes.call_count &gt;= 2</code></pre>
</details>
<div class="desc"><p>Test 'list nodes' invokes node service.</p></div>
</dd>
<dt id="connpy.tests.test_connapp.test_node_mod"><code class="name flex">
<span>def <span class="ident">test_node_mod</span></span>(<span>mock_q_nodes, mock_q_edit, mock_update_node, mock_get_details, mock_list_nodes, app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@patch(&#34;connpy.services.node_service.NodeService.list_nodes&#34;)
@patch(&#34;connpy.services.node_service.NodeService.get_node_details&#34;)
@patch(&#34;connpy.services.node_service.NodeService.update_node&#34;)
@patch(&#34;connpy.cli.forms.Forms.questions_edit&#34;)
@patch(&#34;connpy.cli.forms.Forms.questions_nodes&#34;)
def test_node_mod(mock_q_nodes, mock_q_edit, mock_update_node, mock_get_details, mock_list_nodes, app):
mock_list_nodes.return_value = [&#34;router1&#34;]
mock_get_details.return_value = {&#34;host&#34;: &#34;1.1.1.1&#34;, &#34;port&#34;: 22}
mock_q_edit.return_value = {&#34;host&#34;: True}
mock_q_nodes.return_value = {&#34;host&#34;: &#34;2.2.2.2&#34;, &#34;port&#34;: 22}
app.start([&#34;node&#34;, &#34;-e&#34;, &#34;router1&#34;])
mock_update_node.assert_called_once()</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_connapp.test_node_show"><code class="name flex">
<span>def <span class="ident">test_node_show</span></span>(<span>mock_data, app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@patch(&#34;connpy.printer.data&#34;)
def test_node_show(mock_data, app):
app.nodes_list = [&#34;router1&#34;]
app.config.getitem = MagicMock(return_value={&#34;host&#34;: &#34;1.1.1.1&#34;})
app.start([&#34;node&#34;, &#34;-s&#34;, &#34;router1&#34;])
mock_data.assert_called()</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_connapp.test_plugin_add"><code class="name flex">
<span>def <span class="ident">test_plugin_add</span></span>(<span>mock_verify, mock_copy, mock_exists, app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@patch(&#34;os.path.exists&#34;)
@patch(&#34;shutil.copy2&#34;)
@patch(&#34;connpy.plugins.Plugins.verify_script&#34;)
def test_plugin_add(mock_verify, mock_copy, mock_exists, app):
def mock_exists_side_effect(path):
if &#34;testplug.py&#34; in path: return False
if &#34;testplug.py.bkp&#34; in path: return False
if &#34;file.py&#34; in path: return True
return True
mock_exists.side_effect = mock_exists_side_effect
mock_verify.return_value = None
app.commands = []
app.start([&#34;plugin&#34;, &#34;--add&#34;, &#34;testplug&#34;, &#34;file.py&#34;])
mock_copy.assert_called()</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_connapp.test_plugin_delete"><code class="name flex">
<span>def <span class="ident">test_plugin_delete</span></span>(<span>mock_delete, app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@patch(&#34;connpy.services.plugin_service.PluginService.delete_plugin&#34;)
def test_plugin_delete(mock_delete, app):
app.start([&#34;plugin&#34;, &#34;--del&#34;, &#34;testplug&#34;])
mock_delete.assert_called_once_with(&#34;testplug&#34;)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_connapp.test_plugin_disable"><code class="name flex">
<span>def <span class="ident">test_plugin_disable</span></span>(<span>mock_disable, app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@patch(&#34;connpy.services.plugin_service.PluginService.disable_plugin&#34;)
def test_plugin_disable(mock_disable, app):
app.start([&#34;plugin&#34;, &#34;--disable&#34;, &#34;testplug&#34;])
mock_disable.assert_called_once_with(&#34;testplug&#34;)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_connapp.test_plugin_enable"><code class="name flex">
<span>def <span class="ident">test_plugin_enable</span></span>(<span>mock_enable, app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@patch(&#34;connpy.services.plugin_service.PluginService.enable_plugin&#34;)
def test_plugin_enable(mock_enable, app):
app.start([&#34;plugin&#34;, &#34;--enable&#34;, &#34;testplug&#34;])
mock_enable.assert_called_once_with(&#34;testplug&#34;)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_connapp.test_plugin_list"><code class="name flex">
<span>def <span class="ident">test_plugin_list</span></span>(<span>mock_list_plugins, app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@patch(&#34;connpy.services.plugin_service.PluginService.list_plugins&#34;)
def test_plugin_list(mock_list_plugins, app):
mock_list_plugins.return_value = {&#34;testplug&#34;: {&#34;enabled&#34;: True}}
app.start([&#34;plugin&#34;, &#34;--list&#34;])
mock_list_plugins.assert_called_once()</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_connapp.test_profile_add"><code class="name flex">
<span>def <span class="ident">test_profile_add</span></span>(<span>mock_q_profiles, mock_add_profile, mock_list_profiles, app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@patch(&#34;connpy.services.profile_service.ProfileService.list_profiles&#34;)
@patch(&#34;connpy.services.profile_service.ProfileService.add_profile&#34;)
@patch(&#34;connpy.cli.forms.Forms.questions_profiles&#34;)
def test_profile_add(mock_q_profiles, mock_add_profile, mock_list_profiles, app):
mock_list_profiles.return_value = [&#34;default&#34;]
mock_q_profiles.return_value = {&#34;host&#34;: &#34;test&#34;}
app.start([&#34;profile&#34;, &#34;-a&#34;, &#34;new_profile&#34;])
mock_add_profile.assert_called_once_with(&#34;new_profile&#34;, {&#34;host&#34;: &#34;test&#34;})</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_connapp.test_profile_del"><code class="name flex">
<span>def <span class="ident">test_profile_del</span></span>(<span>mock_prompt, mock_delete_profile, mock_get_profile, app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@patch(&#34;connpy.services.profile_service.ProfileService.get_profile&#34;)
@patch(&#34;connpy.services.profile_service.ProfileService.delete_profile&#34;)
@patch(&#34;inquirer.prompt&#34;)
def test_profile_del(mock_prompt, mock_delete_profile, mock_get_profile, app):
mock_get_profile.return_value = {&#34;host&#34;: &#34;test&#34;}
mock_prompt.return_value = {&#34;delete&#34;: True}
app.start([&#34;profile&#34;, &#34;-r&#34;, &#34;test_profile&#34;])
mock_delete_profile.assert_called_once_with(&#34;test_profile&#34;)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_connapp.test_profile_list"><code class="name flex">
<span>def <span class="ident">test_profile_list</span></span>(<span>mock_print, mock_list_profiles, app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@patch(&#34;connpy.services.profile_service.ProfileService.list_profiles&#34;)
@patch(&#34;connpy.connapp.printer.console.print&#34;)
def test_profile_list(mock_print, mock_list_profiles, app):
&#34;&#34;&#34;Test &#39;profile list&#39; invokes profile service correctly.&#34;&#34;&#34;
mock_list_profiles.return_value = [&#34;default&#34;, &#34;office-user&#34;]
app.start([&#34;list&#34;, &#34;profiles&#34;])
assert mock_list_profiles.call_count &gt;= 2</code></pre>
</details>
<div class="desc"><p>Test 'profile list' invokes profile service correctly.</p></div>
</dd>
<dt id="connpy.tests.test_connapp.test_profile_mod"><code class="name flex">
<span>def <span class="ident">test_profile_mod</span></span>(<span>mock_q_profiles, mock_q_edit, mock_update_profile, mock_get_profile, app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@patch(&#34;connpy.services.profile_service.ProfileService.get_profile&#34;)
@patch(&#34;connpy.services.profile_service.ProfileService.update_profile&#34;)
@patch(&#34;connpy.cli.forms.Forms.questions_edit&#34;)
@patch(&#34;connpy.cli.forms.Forms.questions_profiles&#34;)
def test_profile_mod(mock_q_profiles, mock_q_edit, mock_update_profile, mock_get_profile, app):
mock_get_profile.return_value = {&#34;host&#34;: &#34;test&#34;, &#34;port&#34;: 22}
mock_q_edit.return_value = {&#34;host&#34;: True}
mock_q_profiles.return_value = {&#34;id&#34;: &#34;test_profile&#34;, &#34;host&#34;: &#34;new_host&#34;, &#34;port&#34;: 22}
app.start([&#34;profile&#34;, &#34;-e&#34;, &#34;test_profile&#34;])
mock_update_profile.assert_called_once_with(&#34;test_profile&#34;, {&#34;id&#34;: &#34;test_profile&#34;, &#34;host&#34;: &#34;new_host&#34;, &#34;port&#34;: 22})</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_connapp.test_profile_show"><code class="name flex">
<span>def <span class="ident">test_profile_show</span></span>(<span>mock_data, mock_get_profile, app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@patch(&#34;connpy.services.profile_service.ProfileService.get_profile&#34;)
@patch(&#34;connpy.printer.data&#34;)
def test_profile_show(mock_data, mock_get_profile, app):
mock_get_profile.return_value = {&#34;host&#34;: &#34;test&#34;}
app.start([&#34;profile&#34;, &#34;-s&#34;, &#34;test_profile&#34;])
mock_data.assert_called()</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_connapp.test_run"><code class="name flex">
<span>def <span class="ident">test_run</span></span>(<span>mock_run_commands, app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@patch(&#34;connpy.services.execution_service.ExecutionService.run_commands&#34;)
def test_run(mock_run_commands, app):
app.start([&#34;run&#34;, &#34;node1&#34;, &#34;command1&#34;, &#34;command2&#34;])
mock_run_commands.assert_called_once()
assert mock_run_commands.call_args[1][&#34;nodes_filter&#34;] == &#34;node1&#34;
assert mock_run_commands.call_args[1][&#34;commands&#34;] == [&#34;command1 command2&#34;]</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_connapp.test_type_node_reserved_word"><code class="name flex">
<span>def <span class="ident">test_type_node_reserved_word</span></span>(<span>app)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def test_type_node_reserved_word(app):
app.commands = [&#34;bulk&#34;, &#34;ai&#34;, &#34;run&#34;]
with patch(&#34;sys.argv&#34;, [&#34;connpy&#34;, &#34;node&#34;, &#34;-a&#34;, &#34;bulk&#34;]):
with pytest.raises(SystemExit) as exc:
app._type_node(&#34;bulk&#34;)
assert exc.value.code == 2
# In move/copy it also raises because destination cannot be reserved
with patch(&#34;sys.argv&#34;, [&#34;connpy&#34;, &#34;mv&#34;, &#34;test1&#34;, &#34;bulk&#34;]):
with pytest.raises(SystemExit) as exc:
app._type_node(&#34;bulk&#34;)
assert exc.value.code == 2</code></pre>
</details>
<div class="desc"></div>
</dd>
</dl>
</section>
<section>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="connpy.tests" href="index.html">connpy.tests</a></code></li>
</ul>
</li>
<li><h3><a href="#header-functions">Functions</a></h3>
<ul class="">
<li><code><a title="connpy.tests.test_connapp.app" href="#connpy.tests.test_connapp.app">app</a></code></li>
<li><code><a title="connpy.tests.test_connapp.test_ai" href="#connpy.tests.test_connapp.test_ai">test_ai</a></code></li>
<li><code><a title="connpy.tests.test_connapp.test_ai_list" href="#connpy.tests.test_connapp.test_ai_list">test_ai_list</a></code></li>
<li><code><a title="connpy.tests.test_connapp.test_api_debug" href="#connpy.tests.test_connapp.test_api_debug">test_api_debug</a></code></li>
<li><code><a title="connpy.tests.test_connapp.test_api_start" href="#connpy.tests.test_connapp.test_api_start">test_api_start</a></code></li>
<li><code><a title="connpy.tests.test_connapp.test_api_stop" href="#connpy.tests.test_connapp.test_api_stop">test_api_stop</a></code></li>
<li><code><a title="connpy.tests.test_connapp.test_bulk" href="#connpy.tests.test_connapp.test_bulk">test_bulk</a></code></li>
<li><code><a title="connpy.tests.test_connapp.test_config" href="#connpy.tests.test_connapp.test_config">test_config</a></code></li>
<li><code><a title="connpy.tests.test_connapp.test_config_folder" href="#connpy.tests.test_connapp.test_config_folder">test_config_folder</a></code></li>
<li><code><a title="connpy.tests.test_connapp.test_config_various" href="#connpy.tests.test_connapp.test_config_various">test_config_various</a></code></li>
<li><code><a title="connpy.tests.test_connapp.test_connapp_init" href="#connpy.tests.test_connapp.test_connapp_init">test_connapp_init</a></code></li>
<li><code><a title="connpy.tests.test_connapp.test_copy" href="#connpy.tests.test_connapp.test_copy">test_copy</a></code></li>
<li><code><a title="connpy.tests.test_connapp.test_export" href="#connpy.tests.test_connapp.test_export">test_export</a></code></li>
<li><code><a title="connpy.tests.test_connapp.test_import" href="#connpy.tests.test_connapp.test_import">test_import</a></code></li>
<li><code><a title="connpy.tests.test_connapp.test_list_folders" href="#connpy.tests.test_connapp.test_list_folders">test_list_folders</a></code></li>
<li><code><a title="connpy.tests.test_connapp.test_move" href="#connpy.tests.test_connapp.test_move">test_move</a></code></li>
<li><code><a title="connpy.tests.test_connapp.test_node_add" href="#connpy.tests.test_connapp.test_node_add">test_node_add</a></code></li>
<li><code><a title="connpy.tests.test_connapp.test_node_default" href="#connpy.tests.test_connapp.test_node_default">test_node_default</a></code></li>
<li><code><a title="connpy.tests.test_connapp.test_node_del" href="#connpy.tests.test_connapp.test_node_del">test_node_del</a></code></li>
<li><code><a title="connpy.tests.test_connapp.test_node_list" href="#connpy.tests.test_connapp.test_node_list">test_node_list</a></code></li>
<li><code><a title="connpy.tests.test_connapp.test_node_mod" href="#connpy.tests.test_connapp.test_node_mod">test_node_mod</a></code></li>
<li><code><a title="connpy.tests.test_connapp.test_node_show" href="#connpy.tests.test_connapp.test_node_show">test_node_show</a></code></li>
<li><code><a title="connpy.tests.test_connapp.test_plugin_add" href="#connpy.tests.test_connapp.test_plugin_add">test_plugin_add</a></code></li>
<li><code><a title="connpy.tests.test_connapp.test_plugin_delete" href="#connpy.tests.test_connapp.test_plugin_delete">test_plugin_delete</a></code></li>
<li><code><a title="connpy.tests.test_connapp.test_plugin_disable" href="#connpy.tests.test_connapp.test_plugin_disable">test_plugin_disable</a></code></li>
<li><code><a title="connpy.tests.test_connapp.test_plugin_enable" href="#connpy.tests.test_connapp.test_plugin_enable">test_plugin_enable</a></code></li>
<li><code><a title="connpy.tests.test_connapp.test_plugin_list" href="#connpy.tests.test_connapp.test_plugin_list">test_plugin_list</a></code></li>
<li><code><a title="connpy.tests.test_connapp.test_profile_add" href="#connpy.tests.test_connapp.test_profile_add">test_profile_add</a></code></li>
<li><code><a title="connpy.tests.test_connapp.test_profile_del" href="#connpy.tests.test_connapp.test_profile_del">test_profile_del</a></code></li>
<li><code><a title="connpy.tests.test_connapp.test_profile_list" href="#connpy.tests.test_connapp.test_profile_list">test_profile_list</a></code></li>
<li><code><a title="connpy.tests.test_connapp.test_profile_mod" href="#connpy.tests.test_connapp.test_profile_mod">test_profile_mod</a></code></li>
<li><code><a title="connpy.tests.test_connapp.test_profile_show" href="#connpy.tests.test_connapp.test_profile_show">test_profile_show</a></code></li>
<li><code><a title="connpy.tests.test_connapp.test_run" href="#connpy.tests.test_connapp.test_run">test_run</a></code></li>
<li><code><a title="connpy.tests.test_connapp.test_type_node_reserved_word" href="#connpy.tests.test_connapp.test_type_node_reserved_word">test_type_node_reserved_word</a></code></li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.5</a>.</p>
</footer>
</body>
</html>
+5 -3
View File
@@ -115,8 +115,9 @@ el.replaceWith(d);
def test_invalid_protocol_raises(self):
n = self._make_node(protocol=&#34;invalid_proto&#34;)
with pytest.raises(ValueError, match=&#34;Invalid protocol&#34;):
with pytest.raises(SystemExit) as exc:
n._get_cmd()
assert exc.value.code == 1
def test_ssh_cmd_no_user(self):
n = self._make_node(user=&#34;&#34;)
@@ -155,8 +156,9 @@ el.replaceWith(d);
</summary>
<pre><code class="python">def test_invalid_protocol_raises(self):
n = self._make_node(protocol=&#34;invalid_proto&#34;)
with pytest.raises(ValueError, match=&#34;Invalid protocol&#34;):
n._get_cmd()</code></pre>
with pytest.raises(SystemExit) as exc:
n._get_cmd()
assert exc.value.code == 1</code></pre>
</details>
<div class="desc"></div>
</dd>
@@ -0,0 +1,148 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.5">
<title>connpy.tests.test_execution_service API documentation</title>
<meta name="description" content="">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source > summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible;min-width:max-content}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin:1em 0}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
/* Collapse source docstrings */
setTimeout(() => {
[...document.querySelectorAll('.hljs.language-python > .hljs-string')]
.filter(el => el.innerHTML.length > 200 && ['"""', "'''"].includes(el.innerHTML.substring(0, 3)))
.forEach(el => {
let d = document.createElement('details');
d.classList.add('hljs-string');
d.innerHTML = '<summary>"""</summary>' + el.innerHTML.substring(3);
el.replaceWith(d);
});
}, 100);
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>connpy.tests.test_execution_service</code></h1>
</header>
<section id="section-intro">
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-functions">Functions</h2>
<dl>
<dt id="connpy.tests.test_execution_service.test_run_commands_callback"><code class="name flex">
<span>def <span class="ident">test_run_commands_callback</span></span>(<span>populated_config)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def test_run_commands_callback(populated_config):
&#34;&#34;&#34;Test that run_commands correctly passes on_node_complete to the executor.&#34;&#34;&#34;
service = ExecutionService(populated_config)
# Mock the Nodes class in connpy.services.execution_service
with patch(&#34;connpy.services.execution_service.Nodes&#34;) as MockNodes:
mock_executor = MockNodes.return_value
mock_executor.run.return_value = {&#34;router1&#34;: &#34;output&#34;}
callback = MagicMock()
service.run_commands(
nodes_filter=&#34;router1&#34;,
commands=[&#34;show version&#34;],
on_node_complete=callback
)
# Verify executor.run was called with on_complete=callback
# Note: ExecutionService calls executor.run(..., on_complete=on_node_complete, ...)
MockNodes.return_value.run.assert_called_once()
args, kwargs = MockNodes.return_value.run.call_args
assert kwargs[&#34;on_complete&#34;] == callback</code></pre>
</details>
<div class="desc"><p>Test that run_commands correctly passes on_node_complete to the executor.</p></div>
</dd>
<dt id="connpy.tests.test_execution_service.test_test_commands_callback_regression"><code class="name flex">
<span>def <span class="ident">test_test_commands_callback_regression</span></span>(<span>populated_config)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def test_test_commands_callback_regression(populated_config):
&#34;&#34;&#34;
Test that test_commands correctly passes on_node_complete to the executor.
Regression: ExecutionService.test_commands currently ignores on_node_complete.
&#34;&#34;&#34;
service = ExecutionService(populated_config)
with patch(&#34;connpy.services.execution_service.Nodes&#34;) as MockNodes:
mock_executor = MockNodes.return_value
mock_executor.test.return_value = {&#34;router1&#34;: {&#34;PASS&#34;: True}}
callback = MagicMock()
service.test_commands(
nodes_filter=&#34;router1&#34;,
commands=[&#34;show version&#34;],
expected=[&#34;12.4&#34;],
on_node_complete=callback
)
# This is expected to FAIL because ExecutionService.test_commands
# doesn&#39;t pass on_complete to executor.test
MockNodes.return_value.test.assert_called_once()
args, kwargs = MockNodes.return_value.test.call_args
# We expect &#39;on_complete&#39; to be in kwargs and equal to our callback
assert &#34;on_complete&#34; in kwargs, &#34;on_complete parameter missing in call to executor.test&#34;
assert kwargs[&#34;on_complete&#34;] == callback</code></pre>
</details>
<div class="desc"><p>Test that test_commands correctly passes on_node_complete to the executor.
Regression: ExecutionService.test_commands currently ignores on_node_complete.</p></div>
</dd>
</dl>
</section>
<section>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="connpy.tests" href="index.html">connpy.tests</a></code></li>
</ul>
</li>
<li><h3><a href="#header-functions">Functions</a></h3>
<ul class="">
<li><code><a title="connpy.tests.test_execution_service.test_run_commands_callback" href="#connpy.tests.test_execution_service.test_run_commands_callback">test_run_commands_callback</a></code></li>
<li><code><a title="connpy.tests.test_execution_service.test_test_commands_callback_regression" href="#connpy.tests.test_execution_service.test_test_commands_callback_regression">test_test_commands_callback_regression</a></code></li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.5</a>.</p>
</footer>
</body>
</html>
+184
View File
@@ -0,0 +1,184 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.5">
<title>connpy.tests.test_node_service API documentation</title>
<meta name="description" content="">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source > summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible;min-width:max-content}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin:1em 0}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
/* Collapse source docstrings */
setTimeout(() => {
[...document.querySelectorAll('.hljs.language-python > .hljs-string')]
.filter(el => el.innerHTML.length > 200 && ['"""', "'''"].includes(el.innerHTML.substring(0, 3)))
.forEach(el => {
let d = document.createElement('details');
d.classList.add('hljs-string');
d.innerHTML = '<summary>"""</summary>' + el.innerHTML.substring(3);
el.replaceWith(d);
});
}, 100);
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>connpy.tests.test_node_service</code></h1>
</header>
<section id="section-intro">
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-functions">Functions</h2>
<dl>
<dt id="connpy.tests.test_node_service.test_list_nodes_case_sensitivity"><code class="name flex">
<span>def <span class="ident">test_list_nodes_case_sensitivity</span></span>(<span>populated_config)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def test_list_nodes_case_sensitivity(populated_config):
&#34;&#34;&#34;Test that filtering respects the case setting in config.&#34;&#34;&#34;
service = NodeService(populated_config)
# Default case is False (case-insensitive)
nodes = service.list_nodes(filter_str=&#34;ROUTER&#34;)
assert &#34;router1&#34; in nodes</code></pre>
</details>
<div class="desc"><p>Test that filtering respects the case setting in config.</p></div>
</dd>
<dt id="connpy.tests.test_node_service.test_list_nodes_dynamic_formatting"><code class="name flex">
<span>def <span class="ident">test_list_nodes_dynamic_formatting</span></span>(<span>populated_config)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def test_list_nodes_dynamic_formatting(populated_config):
&#34;&#34;&#34;
Test that list_nodes supports dynamic formatting for any node attribute.
Regression: NodeService currently has hardcoded support for name, location, host.
&#34;&#34;&#34;
service = NodeService(populated_config)
# Try to format using &#39;user&#39; and &#39;protocol&#39; which are NOT in the hardcoded list
# (name, location, host)
format_str = &#34;{name} -&gt; {user}@{host} ({protocol})&#34;
# router1: host=10.0.0.1, user=admin, protocol=ssh
# Expected: &#34;router1 -&gt; admin@10.0.0.1 (ssh)&#34;
formatted = service.list_nodes(filter_str=&#34;router1&#34;, format_str=format_str)
assert len(formatted) == 1
# This will FAIL if it only supports {name}, {location}, {host}
assert formatted[0] == &#34;router1 -&gt; admin@10.0.0.1 (ssh)&#34;</code></pre>
</details>
<div class="desc"><p>Test that list_nodes supports dynamic formatting for any node attribute.
Regression: NodeService currently has hardcoded support for name, location, host.</p></div>
</dd>
<dt id="connpy.tests.test_node_service.test_list_nodes_filtering_parity"><code class="name flex">
<span>def <span class="ident">test_list_nodes_filtering_parity</span></span>(<span>populated_config)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def test_list_nodes_filtering_parity(populated_config):
&#34;&#34;&#34;
Test that list_nodes uses literal &#39;in&#39; logic instead of re.search.
Regression: NodeService currently uses re.search in some versions,
but we want to ensure it uses literal &#39;in&#39; for parity.
&#34;&#34;&#34;
service = NodeService(populated_config)
# If it uses &#39;in&#39; logic, &#39;1&#39; should match all nodes containing &#39;1&#39;
# router1, server1@office, db1@datacenter@office
nodes = service.list_nodes(filter_str=&#34;1&#34;)
assert len(nodes) == 3
assert &#34;router1&#34; in nodes
assert &#34;server1@office&#34; in nodes
assert &#34;db1@datacenter@office&#34; in nodes
# Test regex-specific characters.
# NodeService should use re.search, so &#39;^router&#39; will match &#39;router1&#39;.
nodes_regex = service.list_nodes(filter_str=&#34;^router&#34;)
assert &#34;router1&#34; in nodes_regex</code></pre>
</details>
<div class="desc"><p>Test that list_nodes uses literal 'in' logic instead of re.search.
Regression: NodeService currently uses re.search in some versions,
but we want to ensure it uses literal 'in' for parity.</p></div>
</dd>
<dt id="connpy.tests.test_node_service.test_node_editing_parity"><code class="name flex">
<span>def <span class="ident">test_node_editing_parity</span></span>(<span>populated_config)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def test_node_editing_parity(populated_config):
&#34;&#34;&#34;
Test that add_node improperly raises NodeAlreadyExistsError when used for editing.
Regression: connapp._mod calls add_node instead of update_node.
&#34;&#34;&#34;
service = NodeService(populated_config)
# router1 already exists in populated_config
# We confirm that calling add_node with an existing ID raises NodeAlreadyExistsError
# which is why connapp._mod (which calls add_node) is currently broken for editing.
with pytest.raises(NodeAlreadyExistsError):
service.add_node(&#34;router1&#34;, {&#34;host&#34;: &#34;1.1.1.1&#34;})</code></pre>
</details>
<div class="desc"><p>Test that add_node improperly raises NodeAlreadyExistsError when used for editing.
Regression: connapp._mod calls add_node instead of update_node.</p></div>
</dd>
</dl>
</section>
<section>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="connpy.tests" href="index.html">connpy.tests</a></code></li>
</ul>
</li>
<li><h3><a href="#header-functions">Functions</a></h3>
<ul class="">
<li><code><a title="connpy.tests.test_node_service.test_list_nodes_case_sensitivity" href="#connpy.tests.test_node_service.test_list_nodes_case_sensitivity">test_list_nodes_case_sensitivity</a></code></li>
<li><code><a title="connpy.tests.test_node_service.test_list_nodes_dynamic_formatting" href="#connpy.tests.test_node_service.test_list_nodes_dynamic_formatting">test_list_nodes_dynamic_formatting</a></code></li>
<li><code><a title="connpy.tests.test_node_service.test_list_nodes_filtering_parity" href="#connpy.tests.test_node_service.test_list_nodes_filtering_parity">test_list_nodes_filtering_parity</a></code></li>
<li><code><a title="connpy.tests.test_node_service.test_node_editing_parity" href="#connpy.tests.test_node_service.test_node_editing_parity">test_node_editing_parity</a></code></li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.5</a>.</p>
</footer>
</body>
</html>
+197 -1
View File
@@ -98,11 +98,80 @@ el.replaceWith(d);
assert lines[0] == &#34;[i] line1&#34;
# Second line should be indented by len(&#34;[i] &#34;) = 4 chars
assert lines[1].startswith(&#34; line2&#34;)
assert lines[2].startswith(&#34; line3&#34;)</code></pre>
assert lines[2].startswith(&#34; line3&#34;)
def test_data_output(self, capsys):
printer.data(&#34;my title&#34;, &#34;key: value&#34;)
captured = capsys.readouterr()
# Rich output is formatted with ansi escape sequences or box drawing chars
# Just check that title and content appear in the output stream
assert &#34;my title&#34; in captured.out
assert &#34;key&#34; in captured.out
def test_node_panel_pass(self, capsys):
printer.node_panel(&#34;node1&#34;, &#34;output line\n&#34;, 0)
captured = capsys.readouterr()
assert &#34;node1&#34; in captured.out
assert &#34;PASS&#34; in captured.out
assert &#34;output line&#34; in captured.out
def test_node_panel_fail(self, capsys):
printer.node_panel(&#34;node2&#34;, &#34;error line\n&#34;, 1)
captured = capsys.readouterr()
assert &#34;node2&#34; in captured.out
assert &#34;FAIL&#34; in captured.out
assert &#34;error line&#34; in captured.out
def test_test_panel(self, capsys):
printer.test_panel(&#34;node1&#34;, &#34;output&#34;, 0, {&#34;check1&#34;: True, &#34;check2&#34;: False})
captured = capsys.readouterr()
assert &#34;node1&#34; in captured.out
assert &#34;check1&#34; in captured.out
assert &#34;check2&#34; in captured.out
def test_test_summary(self, capsys):
results = {&#34;node1&#34;: {&#34;test1&#34;: True}, &#34;node2&#34;: {&#34;test2&#34;: False}}
printer.test_summary(results)
captured = capsys.readouterr()
assert &#34;node1&#34; in captured.out
assert &#34;node2&#34; in captured.out
assert &#34;test1&#34; in captured.out
assert &#34;test2&#34; in captured.out
def test_header_output(self, capsys):
printer.header(&#34;My Header&#34;)
captured = capsys.readouterr()
assert &#34;My Header&#34; in captured.out
def test_kv_output(self, capsys):
printer.kv(&#34;mykeystring&#34;, &#34;myvaluestring&#34;)
captured = capsys.readouterr()
assert &#34;mykeystring&#34; in captured.out
assert &#34;myvaluestring&#34; in captured.out
def test_confirm_action(self, capsys):
printer.confirm_action(&#34;router1&#34;, &#34;delete&#34;)
captured = capsys.readouterr()
assert &#34;[i] delete: router1&#34; in captured.out</code></pre>
</details>
<div class="desc"></div>
<h3>Methods</h3>
<dl>
<dt id="connpy.tests.test_printer.TestPrinter.test_confirm_action"><code class="name flex">
<span>def <span class="ident">test_confirm_action</span></span>(<span>self, capsys)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def test_confirm_action(self, capsys):
printer.confirm_action(&#34;router1&#34;, &#34;delete&#34;)
captured = capsys.readouterr()
assert &#34;[i] delete: router1&#34; in captured.out</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_printer.TestPrinter.test_custom_output"><code class="name flex">
<span>def <span class="ident">test_custom_output</span></span>(<span>self, capsys)</span>
</code></dt>
@@ -118,6 +187,24 @@ el.replaceWith(d);
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_printer.TestPrinter.test_data_output"><code class="name flex">
<span>def <span class="ident">test_data_output</span></span>(<span>self, capsys)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def test_data_output(self, capsys):
printer.data(&#34;my title&#34;, &#34;key: value&#34;)
captured = capsys.readouterr()
# Rich output is formatted with ansi escape sequences or box drawing chars
# Just check that title and content appear in the output stream
assert &#34;my title&#34; in captured.out
assert &#34;key&#34; in captured.out</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_printer.TestPrinter.test_debug_output"><code class="name flex">
<span>def <span class="ident">test_debug_output</span></span>(<span>self, capsys)</span>
</code></dt>
@@ -148,6 +235,21 @@ el.replaceWith(d);
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_printer.TestPrinter.test_header_output"><code class="name flex">
<span>def <span class="ident">test_header_output</span></span>(<span>self, capsys)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def test_header_output(self, capsys):
printer.header(&#34;My Header&#34;)
captured = capsys.readouterr()
assert &#34;My Header&#34; in captured.out</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_printer.TestPrinter.test_info_output"><code class="name flex">
<span>def <span class="ident">test_info_output</span></span>(<span>self, capsys)</span>
</code></dt>
@@ -163,6 +265,22 @@ el.replaceWith(d);
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_printer.TestPrinter.test_kv_output"><code class="name flex">
<span>def <span class="ident">test_kv_output</span></span>(<span>self, capsys)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def test_kv_output(self, capsys):
printer.kv(&#34;mykeystring&#34;, &#34;myvaluestring&#34;)
captured = capsys.readouterr()
assert &#34;mykeystring&#34; in captured.out
assert &#34;myvaluestring&#34; in captured.out</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_printer.TestPrinter.test_multiline_indentation"><code class="name flex">
<span>def <span class="ident">test_multiline_indentation</span></span>(<span>self, capsys)</span>
</code></dt>
@@ -182,6 +300,40 @@ el.replaceWith(d);
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_printer.TestPrinter.test_node_panel_fail"><code class="name flex">
<span>def <span class="ident">test_node_panel_fail</span></span>(<span>self, capsys)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def test_node_panel_fail(self, capsys):
printer.node_panel(&#34;node2&#34;, &#34;error line\n&#34;, 1)
captured = capsys.readouterr()
assert &#34;node2&#34; in captured.out
assert &#34;FAIL&#34; in captured.out
assert &#34;error line&#34; in captured.out</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_printer.TestPrinter.test_node_panel_pass"><code class="name flex">
<span>def <span class="ident">test_node_panel_pass</span></span>(<span>self, capsys)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def test_node_panel_pass(self, capsys):
printer.node_panel(&#34;node1&#34;, &#34;output line\n&#34;, 0)
captured = capsys.readouterr()
assert &#34;node1&#34; in captured.out
assert &#34;PASS&#34; in captured.out
assert &#34;output line&#34; in captured.out</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_printer.TestPrinter.test_start_output"><code class="name flex">
<span>def <span class="ident">test_start_output</span></span>(<span>self, capsys)</span>
</code></dt>
@@ -212,6 +364,42 @@ el.replaceWith(d);
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_printer.TestPrinter.test_test_panel"><code class="name flex">
<span>def <span class="ident">test_test_panel</span></span>(<span>self, capsys)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def test_test_panel(self, capsys):
printer.test_panel(&#34;node1&#34;, &#34;output&#34;, 0, {&#34;check1&#34;: True, &#34;check2&#34;: False})
captured = capsys.readouterr()
assert &#34;node1&#34; in captured.out
assert &#34;check1&#34; in captured.out
assert &#34;check2&#34; in captured.out</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_printer.TestPrinter.test_test_summary"><code class="name flex">
<span>def <span class="ident">test_test_summary</span></span>(<span>self, capsys)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def test_test_summary(self, capsys):
results = {&#34;node1&#34;: {&#34;test1&#34;: True}, &#34;node2&#34;: {&#34;test2&#34;: False}}
printer.test_summary(results)
captured = capsys.readouterr()
assert &#34;node1&#34; in captured.out
assert &#34;node2&#34; in captured.out
assert &#34;test1&#34; in captured.out
assert &#34;test2&#34; in captured.out</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_printer.TestPrinter.test_warning_output"><code class="name flex">
<span>def <span class="ident">test_warning_output</span></span>(<span>self, capsys)</span>
</code></dt>
@@ -247,13 +435,21 @@ el.replaceWith(d);
<li>
<h4><code><a title="connpy.tests.test_printer.TestPrinter" href="#connpy.tests.test_printer.TestPrinter">TestPrinter</a></code></h4>
<ul class="">
<li><code><a title="connpy.tests.test_printer.TestPrinter.test_confirm_action" href="#connpy.tests.test_printer.TestPrinter.test_confirm_action">test_confirm_action</a></code></li>
<li><code><a title="connpy.tests.test_printer.TestPrinter.test_custom_output" href="#connpy.tests.test_printer.TestPrinter.test_custom_output">test_custom_output</a></code></li>
<li><code><a title="connpy.tests.test_printer.TestPrinter.test_data_output" href="#connpy.tests.test_printer.TestPrinter.test_data_output">test_data_output</a></code></li>
<li><code><a title="connpy.tests.test_printer.TestPrinter.test_debug_output" href="#connpy.tests.test_printer.TestPrinter.test_debug_output">test_debug_output</a></code></li>
<li><code><a title="connpy.tests.test_printer.TestPrinter.test_error_output" href="#connpy.tests.test_printer.TestPrinter.test_error_output">test_error_output</a></code></li>
<li><code><a title="connpy.tests.test_printer.TestPrinter.test_header_output" href="#connpy.tests.test_printer.TestPrinter.test_header_output">test_header_output</a></code></li>
<li><code><a title="connpy.tests.test_printer.TestPrinter.test_info_output" href="#connpy.tests.test_printer.TestPrinter.test_info_output">test_info_output</a></code></li>
<li><code><a title="connpy.tests.test_printer.TestPrinter.test_kv_output" href="#connpy.tests.test_printer.TestPrinter.test_kv_output">test_kv_output</a></code></li>
<li><code><a title="connpy.tests.test_printer.TestPrinter.test_multiline_indentation" href="#connpy.tests.test_printer.TestPrinter.test_multiline_indentation">test_multiline_indentation</a></code></li>
<li><code><a title="connpy.tests.test_printer.TestPrinter.test_node_panel_fail" href="#connpy.tests.test_printer.TestPrinter.test_node_panel_fail">test_node_panel_fail</a></code></li>
<li><code><a title="connpy.tests.test_printer.TestPrinter.test_node_panel_pass" href="#connpy.tests.test_printer.TestPrinter.test_node_panel_pass">test_node_panel_pass</a></code></li>
<li><code><a title="connpy.tests.test_printer.TestPrinter.test_start_output" href="#connpy.tests.test_printer.TestPrinter.test_start_output">test_start_output</a></code></li>
<li><code><a title="connpy.tests.test_printer.TestPrinter.test_success_output" href="#connpy.tests.test_printer.TestPrinter.test_success_output">test_success_output</a></code></li>
<li><code><a title="connpy.tests.test_printer.TestPrinter.test_test_panel" href="#connpy.tests.test_printer.TestPrinter.test_test_panel">test_test_panel</a></code></li>
<li><code><a title="connpy.tests.test_printer.TestPrinter.test_test_summary" href="#connpy.tests.test_printer.TestPrinter.test_test_summary">test_test_summary</a></code></li>
<li><code><a title="connpy.tests.test_printer.TestPrinter.test_warning_output" href="#connpy.tests.test_printer.TestPrinter.test_warning_output">test_warning_output</a></code></li>
</ul>
</li>
+198
View File
@@ -0,0 +1,198 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.5">
<title>connpy.tests.test_profile_service API documentation</title>
<meta name="description" content="">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source > summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible;min-width:max-content}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin:1em 0}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
/* Collapse source docstrings */
setTimeout(() => {
[...document.querySelectorAll('.hljs.language-python > .hljs-string')]
.filter(el => el.innerHTML.length > 200 && ['"""', "'''"].includes(el.innerHTML.substring(0, 3)))
.forEach(el => {
let d = document.createElement('details');
d.classList.add('hljs-string');
d.innerHTML = '<summary>"""</summary>' + el.innerHTML.substring(3);
el.replaceWith(d);
});
}, 100);
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>connpy.tests.test_profile_service</code></h1>
</header>
<section id="section-intro">
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-functions">Functions</h2>
<dl>
<dt id="connpy.tests.test_profile_service.test_delete_default_profile_fails"><code class="name flex">
<span>def <span class="ident">test_delete_default_profile_fails</span></span>(<span>populated_config)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def test_delete_default_profile_fails(populated_config):
&#34;&#34;&#34;Test that deleting the &#39;default&#39; profile is prohibited.&#34;&#34;&#34;
service = ProfileService(populated_config)
from connpy.services.exceptions import InvalidConfigurationError
with pytest.raises(InvalidConfigurationError, match=&#34;Cannot delete the &#39;default&#39; profile&#34;):
service.delete_profile(&#34;default&#34;)</code></pre>
</details>
<div class="desc"><p>Test that deleting the 'default' profile is prohibited.</p></div>
</dd>
<dt id="connpy.tests.test_profile_service.test_delete_used_profile_fails"><code class="name flex">
<span>def <span class="ident">test_delete_used_profile_fails</span></span>(<span>populated_config)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def test_delete_used_profile_fails(populated_config):
&#34;&#34;&#34;Test that deleting a profile used by nodes is prohibited.&#34;&#34;&#34;
service = ProfileService(populated_config)
from connpy.services.exceptions import InvalidConfigurationError
# In populated_config, we need to make sure a node uses a profile
# Let&#39;s add a node that uses &#39;office-user&#39;
populated_config._connections_add(id=&#34;testnode&#34;, host=&#34;1.1.1.1&#34;, user=&#34;@office-user&#34;)
with pytest.raises(InvalidConfigurationError, match=&#34;is used by nodes&#34;):
service.delete_profile(&#34;office-user&#34;)</code></pre>
</details>
<div class="desc"><p>Test that deleting a profile used by nodes is prohibited.</p></div>
</dd>
<dt id="connpy.tests.test_profile_service.test_profile_crud"><code class="name flex">
<span>def <span class="ident">test_profile_crud</span></span>(<span>populated_config)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def test_profile_crud(populated_config):
&#34;&#34;&#34;Test basic CRUD operations for profiles.&#34;&#34;&#34;
service = ProfileService(populated_config)
# List
profiles = service.list_profiles()
assert &#34;default&#34; in profiles
assert &#34;office-user&#34; in profiles
# Get
office = service.get_profile(&#34;office-user&#34;)
assert office[&#34;user&#34;] == &#34;officeadmin&#34;
# Add
new_data = {
&#34;user&#34;: &#34;newadmin&#34;,
&#34;password&#34;: &#34;newpassword&#34;
}
service.add_profile(&#34;new-profile&#34;, new_data)
assert &#34;new-profile&#34; in service.list_profiles()
assert service.get_profile(&#34;new-profile&#34;)[&#34;user&#34;] == &#34;newadmin&#34;
# Update
update_data = {
&#34;user&#34;: &#34;updatedadmin&#34;
}
service.update_profile(&#34;new-profile&#34;, update_data)
assert service.get_profile(&#34;new-profile&#34;)[&#34;user&#34;] == &#34;updatedadmin&#34;
# Delete
service.delete_profile(&#34;new-profile&#34;)
assert &#34;new-profile&#34; not in service.list_profiles()</code></pre>
</details>
<div class="desc"><p>Test basic CRUD operations for profiles.</p></div>
</dd>
<dt id="connpy.tests.test_profile_service.test_profile_inheritance_parity"><code class="name flex">
<span>def <span class="ident">test_profile_inheritance_parity</span></span>(<span>populated_config)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def test_profile_inheritance_parity(populated_config):
&#34;&#34;&#34;
Test that profiles can inherit from other profiles.
Regression: ProfileService currently doesn&#39;t resolve inheritance within profiles.
&#34;&#34;&#34;
service = ProfileService(populated_config)
# Create a profile that inherits from &#39;office-user&#39;
# &#39;office-user&#39; has user=&#39;officeadmin&#39;, password=&#39;officepass&#39;
inherited_data = {
&#34;user&#34;: &#34;@office-user&#34;,
&#34;options&#34;: &#34;-v&#34;
}
service.add_profile(&#34;inherited-profile&#34;, inherited_data)
# When we get the profile, we expect it to be resolved if inheritance is supported
# This is a common pattern in connpy for nodes, but should it work for profiles?
# The task mentions &#34;profile CRUD and inheritance parity&#34;.
profile = service.get_profile(&#34;inherited-profile&#34;)
# If inheritance is resolved, user should be &#39;officeadmin&#39;
# This is expected to FAIL if ProfileService just returns the raw dict.
assert profile[&#34;user&#34;] == &#34;officeadmin&#34;
assert profile[&#34;password&#34;] == &#34;officepass&#34;
assert profile[&#34;options&#34;] == &#34;-v&#34;</code></pre>
</details>
<div class="desc"><p>Test that profiles can inherit from other profiles.
Regression: ProfileService currently doesn't resolve inheritance within profiles.</p></div>
</dd>
</dl>
</section>
<section>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="connpy.tests" href="index.html">connpy.tests</a></code></li>
</ul>
</li>
<li><h3><a href="#header-functions">Functions</a></h3>
<ul class="">
<li><code><a title="connpy.tests.test_profile_service.test_delete_default_profile_fails" href="#connpy.tests.test_profile_service.test_delete_default_profile_fails">test_delete_default_profile_fails</a></code></li>
<li><code><a title="connpy.tests.test_profile_service.test_delete_used_profile_fails" href="#connpy.tests.test_profile_service.test_delete_used_profile_fails">test_delete_used_profile_fails</a></code></li>
<li><code><a title="connpy.tests.test_profile_service.test_profile_crud" href="#connpy.tests.test_profile_service.test_profile_crud">test_profile_crud</a></code></li>
<li><code><a title="connpy.tests.test_profile_service.test_profile_inheritance_parity" href="#connpy.tests.test_profile_service.test_profile_inheritance_parity">test_profile_inheritance_parity</a></code></li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.5</a>.</p>
</footer>
</body>
</html>
+145
View File
@@ -0,0 +1,145 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.5">
<title>connpy.tests.test_provider API documentation</title>
<meta name="description" content="">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source > summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible;min-width:max-content}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin:1em 0}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
/* Collapse source docstrings */
setTimeout(() => {
[...document.querySelectorAll('.hljs.language-python > .hljs-string')]
.filter(el => el.innerHTML.length > 200 && ['"""', "'''"].includes(el.innerHTML.substring(0, 3)))
.forEach(el => {
let d = document.createElement('details');
d.classList.add('hljs-string');
d.innerHTML = '<summary>"""</summary>' + el.innerHTML.substring(3);
el.replaceWith(d);
});
}, 100);
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>connpy.tests.test_provider</code></h1>
</header>
<section id="section-intro">
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-functions">Functions</h2>
<dl>
<dt id="connpy.tests.test_provider.test_service_provider_local_mode"><code class="name flex">
<span>def <span class="ident">test_service_provider_local_mode</span></span>(<span>)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def test_service_provider_local_mode():
config_mock = MagicMock()
with patch(&#34;connpy.services.provider.NodeService&#34;, create=True) as MockNodeService, \
patch(&#34;connpy.services.provider.ProfileService&#34;, create=True), \
patch(&#34;connpy.services.provider.ConfigService&#34;, create=True), \
patch(&#34;connpy.services.provider.PluginService&#34;, create=True), \
patch(&#34;connpy.services.provider.AIService&#34;, create=True), \
patch(&#34;connpy.services.provider.SystemService&#34;, create=True), \
patch(&#34;connpy.services.provider.ExecutionService&#34;, create=True), \
patch(&#34;connpy.services.provider.ImportExportService&#34;, create=True):
provider = ServiceProvider(config_mock, mode=&#34;local&#34;)
assert provider.mode == &#34;local&#34;
assert provider.config == config_mock
# Verify that an attribute was created
assert provider.nodes is not None</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_provider.test_service_provider_remote_mode"><code class="name flex">
<span>def <span class="ident">test_service_provider_remote_mode</span></span>(<span>)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def test_service_provider_remote_mode():
config_mock = MagicMock()
with patch(&#34;connpy.services.provider.ConfigService&#34;, create=True) as MockConfigService, \
patch(&#34;grpc.insecure_channel&#34;, create=True) as MockChannel:
provider = ServiceProvider(config_mock, mode=&#34;remote&#34;, remote_host=&#34;localhost:50051&#34;)
# Verify ConfigService is initialized locally
assert provider.config_svc is not None
# Verify grpc channel was created
MockChannel.assert_called_once_with(&#34;localhost:50051&#34;)
# Verify a stub was assigned
assert provider.nodes is not None</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_provider.test_service_provider_unknown_mode"><code class="name flex">
<span>def <span class="ident">test_service_provider_unknown_mode</span></span>(<span>)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def test_service_provider_unknown_mode():
config_mock = MagicMock()
with pytest.raises(ValueError, match=&#34;Unknown service mode: invalid_mode&#34;):
ServiceProvider(config_mock, mode=&#34;invalid_mode&#34;)</code></pre>
</details>
<div class="desc"></div>
</dd>
</dl>
</section>
<section>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="connpy.tests" href="index.html">connpy.tests</a></code></li>
</ul>
</li>
<li><h3><a href="#header-functions">Functions</a></h3>
<ul class="">
<li><code><a title="connpy.tests.test_provider.test_service_provider_local_mode" href="#connpy.tests.test_provider.test_service_provider_local_mode">test_service_provider_local_mode</a></code></li>
<li><code><a title="connpy.tests.test_provider.test_service_provider_remote_mode" href="#connpy.tests.test_provider.test_service_provider_remote_mode">test_service_provider_remote_mode</a></code></li>
<li><code><a title="connpy.tests.test_provider.test_service_provider_unknown_mode" href="#connpy.tests.test_provider.test_service_provider_unknown_mode">test_service_provider_unknown_mode</a></code></li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.5</a>.</p>
</footer>
</body>
</html>
+156 -192
View File
@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.5">
<title>connpy.tests.test_sync API documentation</title>
<meta name="description" content="Tests for connpy.core_plugins.sync">
<meta name="description" content="Tests for connpy.services.sync_service">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
@@ -36,7 +36,7 @@ el.replaceWith(d);
<h1 class="title">Module <code>connpy.tests.test_sync</code></h1>
</header>
<section id="section-intro">
<p>Tests for connpy.core_plugins.sync</p>
<p>Tests for connpy.services.sync_service</p>
</section>
<section>
</section>
@@ -45,8 +45,8 @@ el.replaceWith(d);
<section>
<h2 class="section-title" id="header-functions">Functions</h2>
<dl>
<dt id="connpy.tests.test_sync.mock_connapp"><code class="name flex">
<span>def <span class="ident">mock_connapp</span></span>(<span>)</span>
<dt id="connpy.tests.test_sync.mock_config"><code class="name flex">
<span>def <span class="ident">mock_config</span></span>(<span>)</span>
</code></dt>
<dd>
<details class="source">
@@ -54,13 +54,15 @@ el.replaceWith(d);
<span>Expand source code</span>
</summary>
<pre><code class="python">@pytest.fixture
def mock_connapp():
app = MagicMock()
app.config.defaultdir = &#34;/fake/dir&#34;
app.config.file = &#34;/fake/dir/config.yaml&#34;
app.config.key = &#34;/fake/dir/.osk&#34;
app.config.config = {&#34;sync&#34;: True}
return app</code></pre>
def mock_config():
config = MagicMock()
config.defaultdir = &#34;/fake/dir&#34;
config.file = &#34;/fake/dir/config.yaml&#34;
config.key = &#34;/fake/dir/.osk&#34;
config.cachefile = &#34;/fake/dir/.cache&#34;
config.fzf_cachefile = &#34;/fake/dir/.fzf_cache&#34;
config.config = {&#34;sync&#34;: True, &#34;sync_remote&#34;: False}
return config</code></pre>
</details>
<div class="desc"></div>
</dd>
@@ -69,79 +71,85 @@ def mock_connapp():
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="connpy.tests.test_sync.TestSyncPlugin"><code class="flex name class">
<span>class <span class="ident">TestSyncPlugin</span></span>
<dt id="connpy.tests.test_sync.TestSyncService"><code class="flex name class">
<span>class <span class="ident">TestSyncService</span></span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class TestSyncPlugin:
def test_init(self, mock_connapp):
s = sync(mock_connapp)
assert s.sync is True
assert s.file == &#34;/fake/dir/config.yaml&#34;
assert s.token_file == &#34;/fake/dir/gtoken.json&#34;
<pre><code class="python">class TestSyncService:
def test_init(self, mock_config):
s = SyncService(mock_config)
assert s.sync_enabled is True
assert s.token_file == os.path.join(&#34;/fake/dir&#34;, &#34;gtoken.json&#34;)
@patch(&#34;connpy.core_plugins.sync.os.path.exists&#34;)
@patch(&#34;connpy.core_plugins.sync.Credentials&#34;)
def test_get_credentials_success(self, MockCreds, mock_exists, mock_connapp):
@patch(&#34;connpy.services.sync_service.os.path.exists&#34;)
@patch(&#34;connpy.services.sync_service.Credentials&#34;)
def test_get_credentials_success(self, MockCreds, mock_exists, mock_config):
mock_exists.return_value = True
mock_cred_instance = MagicMock()
mock_cred_instance.valid = True
MockCreds.from_authorized_user_file.return_value = mock_cred_instance
s = sync(mock_connapp)
s = SyncService(mock_config)
creds = s.get_credentials()
assert creds == mock_cred_instance
@patch(&#34;connpy.core_plugins.sync.os.path.exists&#34;)
def test_get_credentials_not_found(self, mock_exists, mock_connapp):
@patch(&#34;connpy.services.sync_service.os.path.exists&#34;)
def test_get_credentials_not_found(self, mock_exists, mock_config):
mock_exists.return_value = False
s = sync(mock_connapp)
assert s.get_credentials() == 0
s = SyncService(mock_config)
assert s.get_credentials() is None
@patch(&#34;connpy.core_plugins.sync.zipfile.ZipFile&#34;)
@patch(&#34;connpy.core_plugins.sync.os.path.basename&#34;)
def test_compress_specific_files(self, mock_basename, MockZipFile, mock_connapp):
@patch(&#34;connpy.services.sync_service.zipfile.ZipFile&#34;)
@patch(&#34;connpy.services.sync_service.os.path.exists&#34;)
@patch(&#34;connpy.services.sync_service.os.path.basename&#34;)
def test_compress_and_upload_local(self, mock_basename, mock_exists, MockZipFile, mock_config):
mock_basename.return_value = &#34;config.yaml&#34;
s = sync(mock_connapp)
mock_exists.return_value = True
s = SyncService(mock_config)
# Mocking list_backups and upload_file to avoid real API calls
s.list_backups = MagicMock(return_value=[])
s.upload_file = MagicMock(return_value=True)
zip_mock = MagicMock()
MockZipFile.return_value.__enter__.return_value = zip_mock
s.compress_specific_files(&#34;/fake/zip.zip&#34;)
zip_mock.write.assert_any_call(s.file, &#34;config.yaml&#34;)
zip_mock.write.assert_any_call(s.key, &#34;.osk&#34;)
s.compress_and_upload()
# Verify zip was created with local config and key
zip_mock.write.assert_any_call(s.config.file, &#34;config.yaml&#34;)
zip_mock.write.assert_any_call(s.config.key, &#34;.osk&#34;)
@patch(&#34;connpy.core_plugins.sync.zipfile.ZipFile&#34;)
@patch(&#34;connpy.core_plugins.sync.os.path.dirname&#34;)
def test_decompress_zip_yaml(self, mock_dirname, MockZipFile, mock_connapp):
@patch(&#34;connpy.services.sync_service.zipfile.ZipFile&#34;)
@patch(&#34;connpy.services.sync_service.os.path.exists&#34;)
@patch(&#34;connpy.services.sync_service.os.path.dirname&#34;)
@patch(&#34;connpy.services.sync_service.os.remove&#34;)
def test_perform_restore(self, mock_remove, mock_dirname, mock_exists, MockZipFile, mock_config):
mock_dirname.return_value = &#34;/fake/dir&#34;
s = sync(mock_connapp)
# Mock exists to return True for key and zip, but False for caches during the cleanup phase
def exists_side_effect(path):
if &#34;.cache&#34; in path or &#34;.fzf_cache&#34; in path:
return False
return True
mock_exists.side_effect = exists_side_effect
s = SyncService(mock_config)
zip_mock = MagicMock()
zip_mock.namelist.return_value = [&#34;config.yaml&#34;, &#34;.osk&#34;]
MockZipFile.return_value.__enter__.return_value = zip_mock
assert s.decompress_zip(&#34;/fake/zip.zip&#34;) == 0
zip_mock.extract.assert_any_call(&#34;config.yaml&#34;, &#34;/fake/dir&#34;)
with patch(&#34;connpy.services.sync_service.yaml.safe_load&#34;) as mock_load:
mock_load.return_value = {&#34;connections&#34;: {}, &#34;profiles&#34;: {}, &#34;config&#34;: {}}
assert s.perform_restore(&#34;/fake/zip.zip&#34;) is True
zip_mock.extract.assert_any_call(&#34;.osk&#34;, &#34;/fake/dir&#34;)
@patch(&#34;connpy.core_plugins.sync.zipfile.ZipFile&#34;)
@patch(&#34;connpy.core_plugins.sync.os.path.dirname&#34;)
def test_decompress_zip_json_fallback(self, mock_dirname, MockZipFile, mock_connapp):
mock_dirname.return_value = &#34;/fake/dir&#34;
s = sync(mock_connapp)
zip_mock = MagicMock()
zip_mock.namelist.return_value = [&#34;config.json&#34;, &#34;.osk&#34;]
MockZipFile.return_value.__enter__.return_value = zip_mock
assert s.decompress_zip(&#34;/fake/old_zip.zip&#34;) == 0
zip_mock.extract.assert_any_call(&#34;config.json&#34;, &#34;/fake/dir&#34;)
@patch.object(sync, &#34;get_credentials&#34;)
@patch(&#34;connpy.core_plugins.sync.build&#34;)
def test_get_appdata_files(self, mock_build, mock_get_credentials, mock_connapp):
@patch.object(SyncService, &#34;get_credentials&#34;)
@patch(&#34;connpy.services.sync_service.build&#34;)
def test_list_backups(self, mock_build, mock_get_credentials, mock_config):
mock_get_credentials.return_value = MagicMock()
mock_service = MagicMock()
mock_build.return_value = mock_service
@@ -152,131 +160,109 @@ def mock_connapp():
]
}
s = sync(mock_connapp)
files = s.get_appdata_files()
s = SyncService(mock_config)
files = s.list_backups()
assert len(files) == 1
assert files[0][&#34;id&#34;] == &#34;1&#34;
assert files[0][&#34;timestamp&#34;] == &#34;1000&#34;
@patch.object(sync, &#34;get_credentials&#34;)
@patch(&#34;connpy.core_plugins.sync.build&#34;)
@patch(&#34;connpy.core_plugins.sync.MediaFileUpload&#34;)
@patch(&#34;connpy.core_plugins.sync.os.path.basename&#34;)
def test_backup_file_to_drive(self, mock_basename, mock_media, mock_build, mock_get_credentials, mock_connapp):
mock_get_credentials.return_value = MagicMock()
mock_basename.return_value = &#34;backup.zip&#34;
mock_service = MagicMock()
mock_build.return_value = mock_service
s = sync(mock_connapp)
assert s.backup_file_to_drive(&#34;/fake/backup.zip&#34;, 1234567890000) == 0
mock_service.files().create.assert_called_once()</code></pre>
assert files[0][&#34;timestamp&#34;] == &#34;1000&#34;</code></pre>
</details>
<div class="desc"></div>
<h3>Methods</h3>
<dl>
<dt id="connpy.tests.test_sync.TestSyncPlugin.test_backup_file_to_drive"><code class="name flex">
<span>def <span class="ident">test_backup_file_to_drive</span></span>(<span>self, mock_basename, mock_media, mock_build, mock_get_credentials, mock_connapp)</span>
<dt id="connpy.tests.test_sync.TestSyncService.test_compress_and_upload_local"><code class="name flex">
<span>def <span class="ident">test_compress_and_upload_local</span></span>(<span>self, mock_basename, mock_exists, MockZipFile, mock_config)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@patch.object(sync, &#34;get_credentials&#34;)
@patch(&#34;connpy.core_plugins.sync.build&#34;)
@patch(&#34;connpy.core_plugins.sync.MediaFileUpload&#34;)
@patch(&#34;connpy.core_plugins.sync.os.path.basename&#34;)
def test_backup_file_to_drive(self, mock_basename, mock_media, mock_build, mock_get_credentials, mock_connapp):
mock_get_credentials.return_value = MagicMock()
mock_basename.return_value = &#34;backup.zip&#34;
mock_service = MagicMock()
mock_build.return_value = mock_service
s = sync(mock_connapp)
assert s.backup_file_to_drive(&#34;/fake/backup.zip&#34;, 1234567890000) == 0
mock_service.files().create.assert_called_once()</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_sync.TestSyncPlugin.test_compress_specific_files"><code class="name flex">
<span>def <span class="ident">test_compress_specific_files</span></span>(<span>self, mock_basename, MockZipFile, mock_connapp)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@patch(&#34;connpy.core_plugins.sync.zipfile.ZipFile&#34;)
@patch(&#34;connpy.core_plugins.sync.os.path.basename&#34;)
def test_compress_specific_files(self, mock_basename, MockZipFile, mock_connapp):
<pre><code class="python">@patch(&#34;connpy.services.sync_service.zipfile.ZipFile&#34;)
@patch(&#34;connpy.services.sync_service.os.path.exists&#34;)
@patch(&#34;connpy.services.sync_service.os.path.basename&#34;)
def test_compress_and_upload_local(self, mock_basename, mock_exists, MockZipFile, mock_config):
mock_basename.return_value = &#34;config.yaml&#34;
s = sync(mock_connapp)
mock_exists.return_value = True
s = SyncService(mock_config)
# Mocking list_backups and upload_file to avoid real API calls
s.list_backups = MagicMock(return_value=[])
s.upload_file = MagicMock(return_value=True)
zip_mock = MagicMock()
MockZipFile.return_value.__enter__.return_value = zip_mock
s.compress_specific_files(&#34;/fake/zip.zip&#34;)
zip_mock.write.assert_any_call(s.file, &#34;config.yaml&#34;)
zip_mock.write.assert_any_call(s.key, &#34;.osk&#34;)</code></pre>
s.compress_and_upload()
# Verify zip was created with local config and key
zip_mock.write.assert_any_call(s.config.file, &#34;config.yaml&#34;)
zip_mock.write.assert_any_call(s.config.key, &#34;.osk&#34;)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_sync.TestSyncPlugin.test_decompress_zip_json_fallback"><code class="name flex">
<span>def <span class="ident">test_decompress_zip_json_fallback</span></span>(<span>self, mock_dirname, MockZipFile, mock_connapp)</span>
<dt id="connpy.tests.test_sync.TestSyncService.test_get_credentials_not_found"><code class="name flex">
<span>def <span class="ident">test_get_credentials_not_found</span></span>(<span>self, mock_exists, mock_config)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@patch(&#34;connpy.core_plugins.sync.zipfile.ZipFile&#34;)
@patch(&#34;connpy.core_plugins.sync.os.path.dirname&#34;)
def test_decompress_zip_json_fallback(self, mock_dirname, MockZipFile, mock_connapp):
mock_dirname.return_value = &#34;/fake/dir&#34;
s = sync(mock_connapp)
zip_mock = MagicMock()
zip_mock.namelist.return_value = [&#34;config.json&#34;, &#34;.osk&#34;]
MockZipFile.return_value.__enter__.return_value = zip_mock
assert s.decompress_zip(&#34;/fake/old_zip.zip&#34;) == 0
zip_mock.extract.assert_any_call(&#34;config.json&#34;, &#34;/fake/dir&#34;)</code></pre>
<pre><code class="python">@patch(&#34;connpy.services.sync_service.os.path.exists&#34;)
def test_get_credentials_not_found(self, mock_exists, mock_config):
mock_exists.return_value = False
s = SyncService(mock_config)
assert s.get_credentials() is None</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_sync.TestSyncPlugin.test_decompress_zip_yaml"><code class="name flex">
<span>def <span class="ident">test_decompress_zip_yaml</span></span>(<span>self, mock_dirname, MockZipFile, mock_connapp)</span>
<dt id="connpy.tests.test_sync.TestSyncService.test_get_credentials_success"><code class="name flex">
<span>def <span class="ident">test_get_credentials_success</span></span>(<span>self, MockCreds, mock_exists, mock_config)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@patch(&#34;connpy.core_plugins.sync.zipfile.ZipFile&#34;)
@patch(&#34;connpy.core_plugins.sync.os.path.dirname&#34;)
def test_decompress_zip_yaml(self, mock_dirname, MockZipFile, mock_connapp):
mock_dirname.return_value = &#34;/fake/dir&#34;
s = sync(mock_connapp)
zip_mock = MagicMock()
zip_mock.namelist.return_value = [&#34;config.yaml&#34;, &#34;.osk&#34;]
MockZipFile.return_value.__enter__.return_value = zip_mock
<pre><code class="python">@patch(&#34;connpy.services.sync_service.os.path.exists&#34;)
@patch(&#34;connpy.services.sync_service.Credentials&#34;)
def test_get_credentials_success(self, MockCreds, mock_exists, mock_config):
mock_exists.return_value = True
mock_cred_instance = MagicMock()
mock_cred_instance.valid = True
MockCreds.from_authorized_user_file.return_value = mock_cred_instance
assert s.decompress_zip(&#34;/fake/zip.zip&#34;) == 0
zip_mock.extract.assert_any_call(&#34;config.yaml&#34;, &#34;/fake/dir&#34;)
zip_mock.extract.assert_any_call(&#34;.osk&#34;, &#34;/fake/dir&#34;)</code></pre>
s = SyncService(mock_config)
creds = s.get_credentials()
assert creds == mock_cred_instance</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_sync.TestSyncPlugin.test_get_appdata_files"><code class="name flex">
<span>def <span class="ident">test_get_appdata_files</span></span>(<span>self, mock_build, mock_get_credentials, mock_connapp)</span>
<dt id="connpy.tests.test_sync.TestSyncService.test_init"><code class="name flex">
<span>def <span class="ident">test_init</span></span>(<span>self, mock_config)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@patch.object(sync, &#34;get_credentials&#34;)
@patch(&#34;connpy.core_plugins.sync.build&#34;)
def test_get_appdata_files(self, mock_build, mock_get_credentials, mock_connapp):
<pre><code class="python">def test_init(self, mock_config):
s = SyncService(mock_config)
assert s.sync_enabled is True
assert s.token_file == os.path.join(&#34;/fake/dir&#34;, &#34;gtoken.json&#34;)</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_sync.TestSyncService.test_list_backups"><code class="name flex">
<span>def <span class="ident">test_list_backups</span></span>(<span>self, mock_build, mock_get_credentials, mock_config)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@patch.object(SyncService, &#34;get_credentials&#34;)
@patch(&#34;connpy.services.sync_service.build&#34;)
def test_list_backups(self, mock_build, mock_get_credentials, mock_config):
mock_get_credentials.return_value = MagicMock()
mock_service = MagicMock()
mock_build.return_value = mock_service
@@ -287,65 +273,45 @@ def test_get_appdata_files(self, mock_build, mock_get_credentials, mock_connapp)
]
}
s = sync(mock_connapp)
files = s.get_appdata_files()
s = SyncService(mock_config)
files = s.list_backups()
assert len(files) == 1
assert files[0][&#34;id&#34;] == &#34;1&#34;
assert files[0][&#34;timestamp&#34;] == &#34;1000&#34;</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_sync.TestSyncPlugin.test_get_credentials_not_found"><code class="name flex">
<span>def <span class="ident">test_get_credentials_not_found</span></span>(<span>self, mock_exists, mock_connapp)</span>
<dt id="connpy.tests.test_sync.TestSyncService.test_perform_restore"><code class="name flex">
<span>def <span class="ident">test_perform_restore</span></span>(<span>self, mock_remove, mock_dirname, mock_exists, MockZipFile, mock_config)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@patch(&#34;connpy.core_plugins.sync.os.path.exists&#34;)
def test_get_credentials_not_found(self, mock_exists, mock_connapp):
mock_exists.return_value = False
s = sync(mock_connapp)
assert s.get_credentials() == 0</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_sync.TestSyncPlugin.test_get_credentials_success"><code class="name flex">
<span>def <span class="ident">test_get_credentials_success</span></span>(<span>self, MockCreds, mock_exists, mock_connapp)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@patch(&#34;connpy.core_plugins.sync.os.path.exists&#34;)
@patch(&#34;connpy.core_plugins.sync.Credentials&#34;)
def test_get_credentials_success(self, MockCreds, mock_exists, mock_connapp):
mock_exists.return_value = True
mock_cred_instance = MagicMock()
mock_cred_instance.valid = True
MockCreds.from_authorized_user_file.return_value = mock_cred_instance
<pre><code class="python">@patch(&#34;connpy.services.sync_service.zipfile.ZipFile&#34;)
@patch(&#34;connpy.services.sync_service.os.path.exists&#34;)
@patch(&#34;connpy.services.sync_service.os.path.dirname&#34;)
@patch(&#34;connpy.services.sync_service.os.remove&#34;)
def test_perform_restore(self, mock_remove, mock_dirname, mock_exists, MockZipFile, mock_config):
mock_dirname.return_value = &#34;/fake/dir&#34;
# Mock exists to return True for key and zip, but False for caches during the cleanup phase
def exists_side_effect(path):
if &#34;.cache&#34; in path or &#34;.fzf_cache&#34; in path:
return False
return True
mock_exists.side_effect = exists_side_effect
s = sync(mock_connapp)
creds = s.get_credentials()
assert creds == mock_cred_instance</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.tests.test_sync.TestSyncPlugin.test_init"><code class="name flex">
<span>def <span class="ident">test_init</span></span>(<span>self, mock_connapp)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def test_init(self, mock_connapp):
s = sync(mock_connapp)
assert s.sync is True
assert s.file == &#34;/fake/dir/config.yaml&#34;
assert s.token_file == &#34;/fake/dir/gtoken.json&#34;</code></pre>
s = SyncService(mock_config)
zip_mock = MagicMock()
zip_mock.namelist.return_value = [&#34;config.yaml&#34;, &#34;.osk&#34;]
MockZipFile.return_value.__enter__.return_value = zip_mock
with patch(&#34;connpy.services.sync_service.yaml.safe_load&#34;) as mock_load:
mock_load.return_value = {&#34;connections&#34;: {}, &#34;profiles&#34;: {}, &#34;config&#34;: {}}
assert s.perform_restore(&#34;/fake/zip.zip&#34;) is True
zip_mock.extract.assert_any_call(&#34;.osk&#34;, &#34;/fake/dir&#34;)</code></pre>
</details>
<div class="desc"></div>
</dd>
@@ -366,22 +332,20 @@ def test_get_credentials_success(self, MockCreds, mock_exists, mock_connapp):
</li>
<li><h3><a href="#header-functions">Functions</a></h3>
<ul class="">
<li><code><a title="connpy.tests.test_sync.mock_connapp" href="#connpy.tests.test_sync.mock_connapp">mock_connapp</a></code></li>
<li><code><a title="connpy.tests.test_sync.mock_config" href="#connpy.tests.test_sync.mock_config">mock_config</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="connpy.tests.test_sync.TestSyncPlugin" href="#connpy.tests.test_sync.TestSyncPlugin">TestSyncPlugin</a></code></h4>
<h4><code><a title="connpy.tests.test_sync.TestSyncService" href="#connpy.tests.test_sync.TestSyncService">TestSyncService</a></code></h4>
<ul class="">
<li><code><a title="connpy.tests.test_sync.TestSyncPlugin.test_backup_file_to_drive" href="#connpy.tests.test_sync.TestSyncPlugin.test_backup_file_to_drive">test_backup_file_to_drive</a></code></li>
<li><code><a title="connpy.tests.test_sync.TestSyncPlugin.test_compress_specific_files" href="#connpy.tests.test_sync.TestSyncPlugin.test_compress_specific_files">test_compress_specific_files</a></code></li>
<li><code><a title="connpy.tests.test_sync.TestSyncPlugin.test_decompress_zip_json_fallback" href="#connpy.tests.test_sync.TestSyncPlugin.test_decompress_zip_json_fallback">test_decompress_zip_json_fallback</a></code></li>
<li><code><a title="connpy.tests.test_sync.TestSyncPlugin.test_decompress_zip_yaml" href="#connpy.tests.test_sync.TestSyncPlugin.test_decompress_zip_yaml">test_decompress_zip_yaml</a></code></li>
<li><code><a title="connpy.tests.test_sync.TestSyncPlugin.test_get_appdata_files" href="#connpy.tests.test_sync.TestSyncPlugin.test_get_appdata_files">test_get_appdata_files</a></code></li>
<li><code><a title="connpy.tests.test_sync.TestSyncPlugin.test_get_credentials_not_found" href="#connpy.tests.test_sync.TestSyncPlugin.test_get_credentials_not_found">test_get_credentials_not_found</a></code></li>
<li><code><a title="connpy.tests.test_sync.TestSyncPlugin.test_get_credentials_success" href="#connpy.tests.test_sync.TestSyncPlugin.test_get_credentials_success">test_get_credentials_success</a></code></li>
<li><code><a title="connpy.tests.test_sync.TestSyncPlugin.test_init" href="#connpy.tests.test_sync.TestSyncPlugin.test_init">test_init</a></code></li>
<li><code><a title="connpy.tests.test_sync.TestSyncService.test_compress_and_upload_local" href="#connpy.tests.test_sync.TestSyncService.test_compress_and_upload_local">test_compress_and_upload_local</a></code></li>
<li><code><a title="connpy.tests.test_sync.TestSyncService.test_get_credentials_not_found" href="#connpy.tests.test_sync.TestSyncService.test_get_credentials_not_found">test_get_credentials_not_found</a></code></li>
<li><code><a title="connpy.tests.test_sync.TestSyncService.test_get_credentials_success" href="#connpy.tests.test_sync.TestSyncService.test_get_credentials_success">test_get_credentials_success</a></code></li>
<li><code><a title="connpy.tests.test_sync.TestSyncService.test_init" href="#connpy.tests.test_sync.TestSyncService.test_init">test_init</a></code></li>
<li><code><a title="connpy.tests.test_sync.TestSyncService.test_list_backups" href="#connpy.tests.test_sync.TestSyncService.test_list_backups">test_list_backups</a></code></li>
<li><code><a title="connpy.tests.test_sync.TestSyncService.test_perform_restore" href="#connpy.tests.test_sync.TestSyncService.test_perform_restore">test_perform_restore</a></code></li>
</ul>
</li>
</ul>