perf(cli,core): lazy load Crypto and rich.markdown modules to boost startup latency and bump to v6.1.1

- Implement lazy `@property` getters for `configfile.privatekey` and `configfile.publickey` to defer RSA key loading and pycryptodome imports until explicitly accessed.
- Move top-level `Crypto` imports inside local methods in `configfile.py`, `core.py`, and `config_service.py`.
- Remove top-level `from rich.markdown import Markdown` across CLI handlers (`node_handler.py`, `ai_handler.py`, `terminal_ui.py`) and `ai.py`, deferring parser load to active rendering calls.
- Reduce CLI startup load latency from ~172 ms to ~49 ms (>3.5x speedup / ~71% latency reduction).
- Regenerate HTML documentation in `docs/` via `pdoc`.
- Bump version to v6.1.1.
This commit is contained in:
2026-08-13 12:55:03 -03:00
parent 0632a510ad
commit 32fe18d27a
15 changed files with 127 additions and 21 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
</p> </p>
# Connpy (v6.1.0) # Connpy (v6.1.1)
[![](https://img.shields.io/pypi/v/connpy.svg?style=flat-square)](https://pypi.org/pypi/connpy/) [![](https://img.shields.io/pypi/v/connpy.svg?style=flat-square)](https://pypi.org/pypi/connpy/)
[![](https://img.shields.io/pypi/pyversions/connpy.svg?style=flat-square)](https://pypi.org/pypi/connpy/) [![](https://img.shields.io/pypi/pyversions/connpy.svg?style=flat-square)](https://pypi.org/pypi/connpy/)
[![](https://img.shields.io/pypi/dm/connpy.svg?style=flat-square&cacheSeconds=86400)](https://pypi.org/pypi/connpy/) [![](https://img.shields.io/pypi/dm/connpy.svg?style=flat-square&cacheSeconds=86400)](https://pypi.org/pypi/connpy/)
+1 -1
View File
@@ -5,7 +5,7 @@
</p> </p>
# Connpy (v6.1.0) # Connpy (v6.1.1)
[![](https://img.shields.io/pypi/v/connpy.svg?style=flat-square)](https://pypi.org/pypi/connpy/) [![](https://img.shields.io/pypi/v/connpy.svg?style=flat-square)](https://pypi.org/pypi/connpy/)
[![](https://img.shields.io/pypi/pyversions/connpy.svg?style=flat-square)](https://pypi.org/pypi/connpy/) [![](https://img.shields.io/pypi/pyversions/connpy.svg?style=flat-square)](https://pypi.org/pypi/connpy/)
[![](https://img.shields.io/pypi/dm/connpy.svg?style=flat-square&cacheSeconds=86400)](https://pypi.org/pypi/connpy/) [![](https://img.shields.io/pypi/dm/connpy.svg?style=flat-square&cacheSeconds=86400)](https://pypi.org/pypi/connpy/)
+1 -1
View File
@@ -1 +1 @@
__version__ = "6.1.0" __version__ = "6.1.1"
+3 -1
View File
@@ -33,7 +33,6 @@ def stream_chunk_builder(*args, **kwargs):
return _stream_chunk_builder(*args, **kwargs) return _stream_chunk_builder(*args, **kwargs)
from .hooks import ClassHook, MethodHook from .hooks import ClassHook, MethodHook
from . import printer from . import printer
from rich.markdown import Markdown
from rich.panel import Panel from rich.panel import Panel
from rich.text import Text from rich.text import Text
from rich.console import Group from rich.console import Group
@@ -1183,6 +1182,7 @@ class ai:
if status: if status:
try: status.stop() try: status.stop()
except: pass except: pass
from rich.markdown import Markdown
self.console.print(Panel(Markdown(resp_msg.content), title=f"[{current_brain}][bold]{label} Reasoning[/bold][/{current_brain}]", border_style="architect" if current_brain == "architect" else "engineer")) self.console.print(Panel(Markdown(resp_msg.content), title=f"[{current_brain}][bold]{label} Reasoning[/bold][/{current_brain}]", border_style="architect" if current_brain == "architect" else "engineer"))
if status: if status:
try: status.start() try: status.start()
@@ -1230,6 +1230,7 @@ class ai:
if status: if status:
try: status.stop() try: status.stop()
except: pass except: pass
from rich.markdown import Markdown
self.console.print(Panel(Markdown(obs), title="[architect]Architect Consultation[/architect]", border_style="architect")) self.console.print(Panel(Markdown(obs), title="[architect]Architect Consultation[/architect]", border_style="architect"))
if status: if status:
try: status.start() try: status.start()
@@ -1872,6 +1873,7 @@ class PlaybookBuilderAgent:
chunk_callback(resp_msg.content) chunk_callback(resp_msg.content)
elif not resp_msg.tool_calls: elif not resp_msg.tool_calls:
# In direct non-streaming output, print markdown # In direct non-streaming output, print markdown
from rich.markdown import Markdown
self.console.print(Markdown(resp_msg.content)) self.console.print(Markdown(resp_msg.content))
if not resp_msg.tool_calls: if not resp_msg.tool_calls:
+2 -1
View File
@@ -1,6 +1,5 @@
import sys import sys
from rich.panel import Panel from rich.panel import Panel
from rich.markdown import Markdown
from rich.rule import Rule from rich.rule import Rule
from rich.prompt import Prompt from rich.prompt import Prompt
@@ -102,6 +101,7 @@ class AIHandler:
title = "[architect][bold]Network Architect[/bold][/architect]" if responder == "architect" else "[engineer][bold]Network Engineer[/bold][/engineer]" title = "[architect][bold]Network Architect[/bold][/architect]" if responder == "architect" else "[engineer][bold]Network Engineer[/bold][/engineer]"
if not result.get("streamed"): if not result.get("streamed"):
from rich.markdown import Markdown
mdprint(Panel(Markdown(result["response"]), title=title, border_style=border, expand=False)) mdprint(Panel(Markdown(result["response"]), title=title, border_style=border, expand=False))
if "usage" in result: if "usage" in result:
@@ -121,6 +121,7 @@ class AIHandler:
printer.info(f"Session '{session_id}' not found. Starting clean.") printer.info(f"Session '{session_id}' not found. Starting clean.")
if not history: if not history:
from rich.markdown import Markdown
mdprint(Rule(style="engineer")) mdprint(Rule(style="engineer"))
mdprint(Markdown("**Networking Expert Agent**: Hi! I'm your assistant. I can help you diagnose issues, run commands, and manage your nodes.\nType 'exit' to quit.\n")) mdprint(Markdown("**Networking Expert Agent**: Hi! I'm your assistant. I can help you diagnose issues, run commands, and manage your nodes.\nType 'exit' to quit.\n"))
mdprint(Rule(style="engineer")) mdprint(Rule(style="engineer"))
+1 -1
View File
@@ -1,6 +1,5 @@
import sys import sys
import yaml import yaml
from rich.markdown import Markdown
from .. import printer from .. import printer
from ..services.exceptions import ConnpyError, InvalidConfigurationError from ..services.exceptions import ConnpyError, InvalidConfigurationError
@@ -156,6 +155,7 @@ class NodeHandler:
# Fast fail if parent folder does not exist # Fast fail if parent folder does not exist
self.app.services.nodes.validate_parent_folder(args.data) self.app.services.nodes.validate_parent_folder(args.data)
from rich.markdown import Markdown
printer.console.print(Markdown(get_instructions())) printer.console.print(Markdown(get_instructions()))
new_node_data = self.forms.questions_nodes(args.data, uniques) new_node_data = self.forms.questions_nodes(args.data, uniques)
+1 -1
View File
@@ -11,7 +11,6 @@ from textwrap import dedent
from rich.console import Console from rich.console import Console
from rich.panel import Panel from rich.panel import Panel
from rich.markdown import Markdown
from prompt_toolkit import PromptSession from prompt_toolkit import PromptSession
from prompt_toolkit.key_binding import KeyBindings from prompt_toolkit.key_binding import KeyBindings
from prompt_toolkit.filters import has_completions from prompt_toolkit.filters import has_completions
@@ -459,6 +458,7 @@ class CopilotInterface:
# If no chunks were streamed but we have a guide, print it as a panel # If no chunks were streamed but we have a guide, print it as a panel
if first_chunk and result and result.get("guide"): if first_chunk and result and result.get("guide"):
from rich.markdown import Markdown
self.console.print(Panel(Markdown(result["guide"]), title=f"[bold {persona_color}]{persona_title}[/bold {persona_color}]", border_style=persona_color)) self.console.print(Panel(Markdown(result["guide"]), title=f"[bold {persona_color}]{persona_title}[/bold {persona_color}]", border_style=persona_color))
commands = result.get("commands", []) commands = result.get("commands", [])
+29 -5
View File
@@ -6,8 +6,6 @@ import re
import sys import sys
import yaml import yaml
import shutil import shutil
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
from pathlib import Path from pathlib import Path
from copy import deepcopy from copy import deepcopy
from .hooks import MethodHook, ClassHook from .hooks import MethodHook, ClassHook
@@ -139,16 +137,39 @@ class configfile:
self.connections = config["connections"] self.connections = config["connections"]
self.profiles = config["profiles"] self.profiles = config["profiles"]
self._privatekey_obj = None
self._publickey_obj = None
if not os.path.exists(self.key): if not os.path.exists(self.key):
self._createkey(self.key) self._createkey(self.key)
with open(self.key) as f:
self.privatekey = RSA.import_key(f.read())
self.publickey = self.privatekey.publickey()
# Self-heal text caches if they are missing # Self-heal text caches if they are missing
if not os.path.exists(self.fzf_cachefile) or not os.path.exists(self.folders_cachefile) or not os.path.exists(self.profiles_cachefile): if not os.path.exists(self.fzf_cachefile) or not os.path.exists(self.folders_cachefile) or not os.path.exists(self.profiles_cachefile):
self._generate_nodes_cache() self._generate_nodes_cache()
@property
def privatekey(self):
if getattr(self, '_privatekey_obj', None) is None:
from Crypto.PublicKey import RSA
if not os.path.exists(self.key):
self._createkey(self.key)
with open(self.key) as f:
self._privatekey_obj = RSA.import_key(f.read())
return self._privatekey_obj
@privatekey.setter
def privatekey(self, value):
self._privatekey_obj = value
@property
def publickey(self):
if getattr(self, '_publickey_obj', None) is None:
self._publickey_obj = self.privatekey.publickey()
return self._publickey_obj
@publickey.setter
def publickey(self, value):
self._publickey_obj = value
def get_effective_setting(self, key, default=None): def get_effective_setting(self, key, default=None):
"""Get config setting with shared fallback for inheritable keys.""" """Get config setting with shared fallback for inheritable keys."""
@@ -297,6 +318,7 @@ class configfile:
def _createkey(self, keyfile): def _createkey(self, keyfile):
#Create key file #Create key file
from Crypto.PublicKey import RSA
key = RSA.generate(2048) key = RSA.generate(2048)
with open(keyfile,'wb') as f: with open(keyfile,'wb') as f:
f.write(key.export_key('PEM')) f.write(key.export_key('PEM'))
@@ -623,6 +645,8 @@ class configfile:
if keyfile is None: if keyfile is None:
keyfile = self.key keyfile = self.key
with open(keyfile) as f: with open(keyfile) as f:
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
key = RSA.import_key(f.read()) key = RSA.import_key(f.read())
f.close() f.close()
publickey = key.publickey() publickey = key.publickey()
+2 -2
View File
@@ -4,8 +4,6 @@ import os
import re import re
import pexpect import pexpect
import shlex import shlex
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
import ast import ast
from time import sleep,time from time import sleep,time
import datetime import datetime
@@ -274,6 +272,8 @@ class node:
if keyfile is None: if keyfile is None:
keyfile = self.key keyfile = self.key
if keyfile is not None: if keyfile is not None:
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
with open(keyfile) as f: with open(keyfile) as f:
key = RSA.import_key(f.read()) key = RSA.import_key(f.read())
decryptor = PKCS1_OAEP.new(key) decryptor = PKCS1_OAEP.new(key)
-2
View File
@@ -2,8 +2,6 @@ import os
import shutil import shutil
import base64 import base64
from typing import Any, Dict from typing import Any, Dict
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
from .base import BaseService from .base import BaseService
from .exceptions import ConnpyError, InvalidConfigurationError, NodeNotFoundError from .exceptions import ConnpyError, InvalidConfigurationError, NodeNotFoundError
+6
View File
@@ -255,6 +255,7 @@ el.replaceWith(d);
chunk_callback(resp_msg.content) chunk_callback(resp_msg.content)
elif not resp_msg.tool_calls: elif not resp_msg.tool_calls:
# In direct non-streaming output, print markdown # In direct non-streaming output, print markdown
from rich.markdown import Markdown
self.console.print(Markdown(resp_msg.content)) self.console.print(Markdown(resp_msg.content))
if not resp_msg.tool_calls: if not resp_msg.tool_calls:
@@ -388,6 +389,7 @@ el.replaceWith(d);
chunk_callback(resp_msg.content) chunk_callback(resp_msg.content)
elif not resp_msg.tool_calls: elif not resp_msg.tool_calls:
# In direct non-streaming output, print markdown # In direct non-streaming output, print markdown
from rich.markdown import Markdown
self.console.print(Markdown(resp_msg.content)) self.console.print(Markdown(resp_msg.content))
if not resp_msg.tool_calls: if not resp_msg.tool_calls:
@@ -1621,6 +1623,7 @@ class ai:
if status: if status:
try: status.stop() try: status.stop()
except: pass except: pass
from rich.markdown import Markdown
self.console.print(Panel(Markdown(resp_msg.content), title=f&#34;[{current_brain}][bold]{label} Reasoning[/bold][/{current_brain}]&#34;, border_style=&#34;architect&#34; if current_brain == &#34;architect&#34; else &#34;engineer&#34;)) self.console.print(Panel(Markdown(resp_msg.content), title=f&#34;[{current_brain}][bold]{label} Reasoning[/bold][/{current_brain}]&#34;, border_style=&#34;architect&#34; if current_brain == &#34;architect&#34; else &#34;engineer&#34;))
if status: if status:
try: status.start() try: status.start()
@@ -1668,6 +1671,7 @@ class ai:
if status: if status:
try: status.stop() try: status.stop()
except: pass except: pass
from rich.markdown import Markdown
self.console.print(Panel(Markdown(obs), title=&#34;[architect]Architect Consultation[/architect]&#34;, border_style=&#34;architect&#34;)) self.console.print(Panel(Markdown(obs), title=&#34;[architect]Architect Consultation[/architect]&#34;, border_style=&#34;architect&#34;))
if status: if status:
try: status.start() try: status.start()
@@ -2579,6 +2583,7 @@ def ask(self, user_input, dryrun=False, chat_history=None, status=None, debug=Fa
if status: if status:
try: status.stop() try: status.stop()
except: pass except: pass
from rich.markdown import Markdown
self.console.print(Panel(Markdown(resp_msg.content), title=f&#34;[{current_brain}][bold]{label} Reasoning[/bold][/{current_brain}]&#34;, border_style=&#34;architect&#34; if current_brain == &#34;architect&#34; else &#34;engineer&#34;)) self.console.print(Panel(Markdown(resp_msg.content), title=f&#34;[{current_brain}][bold]{label} Reasoning[/bold][/{current_brain}]&#34;, border_style=&#34;architect&#34; if current_brain == &#34;architect&#34; else &#34;engineer&#34;))
if status: if status:
try: status.start() try: status.start()
@@ -2626,6 +2631,7 @@ def ask(self, user_input, dryrun=False, chat_history=None, status=None, debug=Fa
if status: if status:
try: status.stop() try: status.stop()
except: pass except: pass
from rich.markdown import Markdown
self.console.print(Panel(Markdown(obs), title=&#34;[architect]Architect Consultation[/architect]&#34;, border_style=&#34;architect&#34;)) self.console.print(Panel(Markdown(obs), title=&#34;[architect]Architect Consultation[/architect]&#34;, border_style=&#34;architect&#34;))
if status: if status:
try: status.start() try: status.start()
+4
View File
@@ -148,6 +148,7 @@ el.replaceWith(d);
title = &#34;[architect][bold]Network Architect[/bold][/architect]&#34; if responder == &#34;architect&#34; else &#34;[engineer][bold]Network Engineer[/bold][/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;): if not result.get(&#34;streamed&#34;):
from rich.markdown import Markdown
mdprint(Panel(Markdown(result[&#34;response&#34;]), title=title, border_style=border, expand=False)) mdprint(Panel(Markdown(result[&#34;response&#34;]), title=title, border_style=border, expand=False))
if &#34;usage&#34; in result: if &#34;usage&#34; in result:
@@ -167,6 +168,7 @@ el.replaceWith(d);
printer.info(f&#34;Session &#39;{session_id}&#39; not found. Starting clean.&#34;) printer.info(f&#34;Session &#39;{session_id}&#39; not found. Starting clean.&#34;)
if not history: if not history:
from rich.markdown import Markdown
mdprint(Rule(style=&#34;engineer&#34;)) 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(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;)) mdprint(Rule(style=&#34;engineer&#34;))
@@ -573,6 +575,7 @@ el.replaceWith(d);
printer.info(f&#34;Session &#39;{session_id}&#39; not found. Starting clean.&#34;) printer.info(f&#34;Session &#39;{session_id}&#39; not found. Starting clean.&#34;)
if not history: if not history:
from rich.markdown import Markdown
mdprint(Rule(style=&#34;engineer&#34;)) 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(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;)) mdprint(Rule(style=&#34;engineer&#34;))
@@ -626,6 +629,7 @@ el.replaceWith(d);
title = &#34;[architect][bold]Network Architect[/bold][/architect]&#34; if responder == &#34;architect&#34; else &#34;[engineer][bold]Network Engineer[/bold][/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;): if not result.get(&#34;streamed&#34;):
from rich.markdown import Markdown
mdprint(Panel(Markdown(result[&#34;response&#34;]), title=title, border_style=border, expand=False)) mdprint(Panel(Markdown(result[&#34;response&#34;]), title=title, border_style=border, expand=False))
if &#34;usage&#34; in result: if &#34;usage&#34; in result:
+2
View File
@@ -204,6 +204,7 @@ el.replaceWith(d);
# Fast fail if parent folder does not exist # Fast fail if parent folder does not exist
self.app.services.nodes.validate_parent_folder(args.data) self.app.services.nodes.validate_parent_folder(args.data)
from rich.markdown import Markdown
printer.console.print(Markdown(get_instructions())) printer.console.print(Markdown(get_instructions()))
new_node_data = self.forms.questions_nodes(args.data, uniques) new_node_data = self.forms.questions_nodes(args.data, uniques)
@@ -374,6 +375,7 @@ def forms(self):
# Fast fail if parent folder does not exist # Fast fail if parent folder does not exist
self.app.services.nodes.validate_parent_folder(args.data) self.app.services.nodes.validate_parent_folder(args.data)
from rich.markdown import Markdown
printer.console.print(Markdown(get_instructions())) printer.console.print(Markdown(get_instructions()))
new_node_data = self.forms.questions_nodes(args.data, uniques) new_node_data = self.forms.questions_nodes(args.data, uniques)
+2
View File
@@ -492,6 +492,7 @@ el.replaceWith(d);
# If no chunks were streamed but we have a guide, print it as a panel # If no chunks were streamed but we have a guide, print it as a panel
if first_chunk and result and result.get(&#34;guide&#34;): if first_chunk and result and result.get(&#34;guide&#34;):
from rich.markdown import Markdown
self.console.print(Panel(Markdown(result[&#34;guide&#34;]), title=f&#34;[bold {persona_color}]{persona_title}[/bold {persona_color}]&#34;, border_style=persona_color)) self.console.print(Panel(Markdown(result[&#34;guide&#34;]), title=f&#34;[bold {persona_color}]{persona_title}[/bold {persona_color}]&#34;, border_style=persona_color))
commands = result.get(&#34;commands&#34;, []) commands = result.get(&#34;commands&#34;, [])
@@ -997,6 +998,7 @@ el.replaceWith(d);
# If no chunks were streamed but we have a guide, print it as a panel # If no chunks were streamed but we have a guide, print it as a panel
if first_chunk and result and result.get(&#34;guide&#34;): if first_chunk and result and result.get(&#34;guide&#34;):
from rich.markdown import Markdown
self.console.print(Panel(Markdown(result[&#34;guide&#34;]), title=f&#34;[bold {persona_color}]{persona_title}[/bold {persona_color}]&#34;, border_style=persona_color)) self.console.print(Panel(Markdown(result[&#34;guide&#34;]), title=f&#34;[bold {persona_color}]{persona_title}[/bold {persona_color}]&#34;, border_style=persona_color))
commands = result.get(&#34;commands&#34;, []) commands = result.get(&#34;commands&#34;, [])
+72 -5
View File
@@ -41,7 +41,7 @@ el.replaceWith(d);
<p align="center"> <p align="center">
<img src="https://nginx.gederico.dynu.net/images/CONNPY-resized.png" alt="App Logo"> <img src="https://nginx.gederico.dynu.net/images/CONNPY-resized.png" alt="App Logo">
</p> </p>
<h1 id="connpy-v610">Connpy (v6.1.0)</h1> <h1 id="connpy-v611">Connpy (v6.1.1)</h1>
<p><a href="https://pypi.org/pypi/connpy/"><img alt="" src="https://img.shields.io/pypi/v/connpy.svg?style=flat-square"></a> <p><a href="https://pypi.org/pypi/connpy/"><img alt="" src="https://img.shields.io/pypi/v/connpy.svg?style=flat-square"></a>
<a href="https://pypi.org/pypi/connpy/"><img alt="" src="https://img.shields.io/pypi/pyversions/connpy.svg?style=flat-square"></a> <a href="https://pypi.org/pypi/connpy/"><img alt="" src="https://img.shields.io/pypi/pyversions/connpy.svg?style=flat-square"></a>
<a href="https://pypi.org/pypi/connpy/"><img alt="" src="https://img.shields.io/pypi/dm/connpy.svg?style=flat-square&amp;cacheSeconds=86400"></a> <a href="https://pypi.org/pypi/connpy/"><img alt="" src="https://img.shields.io/pypi/dm/connpy.svg?style=flat-square&amp;cacheSeconds=86400"></a>
@@ -819,16 +819,39 @@ class configfile:
self.connections = config[&#34;connections&#34;] self.connections = config[&#34;connections&#34;]
self.profiles = config[&#34;profiles&#34;] self.profiles = config[&#34;profiles&#34;]
self._privatekey_obj = None
self._publickey_obj = None
if not os.path.exists(self.key): if not os.path.exists(self.key):
self._createkey(self.key) self._createkey(self.key)
with open(self.key) as f:
self.privatekey = RSA.import_key(f.read())
self.publickey = self.privatekey.publickey()
# Self-heal text caches if they are missing # Self-heal text caches if they are missing
if not os.path.exists(self.fzf_cachefile) or not os.path.exists(self.folders_cachefile) or not os.path.exists(self.profiles_cachefile): if not os.path.exists(self.fzf_cachefile) or not os.path.exists(self.folders_cachefile) or not os.path.exists(self.profiles_cachefile):
self._generate_nodes_cache() self._generate_nodes_cache()
@property
def privatekey(self):
if getattr(self, &#39;_privatekey_obj&#39;, None) is None:
from Crypto.PublicKey import RSA
if not os.path.exists(self.key):
self._createkey(self.key)
with open(self.key) as f:
self._privatekey_obj = RSA.import_key(f.read())
return self._privatekey_obj
@privatekey.setter
def privatekey(self, value):
self._privatekey_obj = value
@property
def publickey(self):
if getattr(self, &#39;_publickey_obj&#39;, None) is None:
self._publickey_obj = self.privatekey.publickey()
return self._publickey_obj
@publickey.setter
def publickey(self, value):
self._publickey_obj = value
def get_effective_setting(self, key, default=None): def get_effective_setting(self, key, default=None):
&#34;&#34;&#34;Get config setting with shared fallback for inheritable keys.&#34;&#34;&#34; &#34;&#34;&#34;Get config setting with shared fallback for inheritable keys.&#34;&#34;&#34;
@@ -977,6 +1000,7 @@ class configfile:
def _createkey(self, keyfile): def _createkey(self, keyfile):
#Create key file #Create key file
from Crypto.PublicKey import RSA
key = RSA.generate(2048) key = RSA.generate(2048)
with open(keyfile,&#39;wb&#39;) as f: with open(keyfile,&#39;wb&#39;) as f:
f.write(key.export_key(&#39;PEM&#39;)) f.write(key.export_key(&#39;PEM&#39;))
@@ -1303,6 +1327,8 @@ class configfile:
if keyfile is None: if keyfile is None:
keyfile = self.key keyfile = self.key
with open(keyfile) as f: with open(keyfile) as f:
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
key = RSA.import_key(f.read()) key = RSA.import_key(f.read())
f.close() f.close()
publickey = key.publickey() publickey = key.publickey()
@@ -1331,6 +1357,41 @@ class configfile:
- publickey (obj): Object containing the public key to decrypt - publickey (obj): Object containing the public key to decrypt
passwords. passwords.
</code></pre></div> </code></pre></div>
<h3>Instance variables</h3>
<dl>
<dt id="connpy.configfile.privatekey"><code class="name">prop <span class="ident">privatekey</span></code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@property
def privatekey(self):
if getattr(self, &#39;_privatekey_obj&#39;, None) is None:
from Crypto.PublicKey import RSA
if not os.path.exists(self.key):
self._createkey(self.key)
with open(self.key) as f:
self._privatekey_obj = RSA.import_key(f.read())
return self._privatekey_obj</code></pre>
</details>
<div class="desc"></div>
</dd>
<dt id="connpy.configfile.publickey"><code class="name">prop <span class="ident">publickey</span></code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">@property
def publickey(self):
if getattr(self, &#39;_publickey_obj&#39;, None) is None:
self._publickey_obj = self.privatekey.publickey()
return self._publickey_obj</code></pre>
</details>
<div class="desc"></div>
</dd>
</dl>
<h3>Methods</h3> <h3>Methods</h3>
<dl> <dl>
<dt id="connpy.configfile.encrypt"><code class="name flex"> <dt id="connpy.configfile.encrypt"><code class="name flex">
@@ -1363,6 +1424,8 @@ def encrypt(self, password, keyfile=None):
if keyfile is None: if keyfile is None:
keyfile = self.key keyfile = self.key
with open(keyfile) as f: with open(keyfile) as f:
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
key = RSA.import_key(f.read()) key = RSA.import_key(f.read())
f.close() f.close()
publickey = key.publickey() publickey = key.publickey()
@@ -1839,6 +1902,8 @@ class node:
if keyfile is None: if keyfile is None:
keyfile = self.key keyfile = self.key
if keyfile is not None: if keyfile is not None:
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
with open(keyfile) as f: with open(keyfile) as f:
key = RSA.import_key(f.read()) key = RSA.import_key(f.read())
decryptor = PKCS1_OAEP.new(key) decryptor = PKCS1_OAEP.new(key)
@@ -4022,7 +4087,7 @@ def test(self, commands, expected, vars = None,*, folder = None, prompt = None,
<nav id="sidebar"> <nav id="sidebar">
<div class="toc"> <div class="toc">
<ul> <ul>
<li><a href="#connpy-v610">Connpy (v6.1.0)</a><ul> <li><a href="#connpy-v611">Connpy (v6.1.1)</a><ul>
<li><a href="#1-ai-system">1. 🤖 AI System</a><ul> <li><a href="#1-ai-system">1. 🤖 AI System</a><ul>
<li><a href="#1a-terminal-copilot-ctrlspace">1a. Terminal Copilot (Ctrl+Space)</a></li> <li><a href="#1a-terminal-copilot-ctrlspace">1a. Terminal Copilot (Ctrl+Space)</a></li>
<li><a href="#1b-ai-chat-conn-ai">1b. AI Chat (conn ai)</a></li> <li><a href="#1b-ai-chat-conn-ai">1b. AI Chat (conn ai)</a></li>
@@ -4104,6 +4169,8 @@ def test(self, commands, expected, vars = None,*, folder = None, prompt = None,
<li><code><a title="connpy.configfile.get_effective_setting" href="#connpy.configfile.get_effective_setting">get_effective_setting</a></code></li> <li><code><a title="connpy.configfile.get_effective_setting" href="#connpy.configfile.get_effective_setting">get_effective_setting</a></code></li>
<li><code><a title="connpy.configfile.getitem" href="#connpy.configfile.getitem">getitem</a></code></li> <li><code><a title="connpy.configfile.getitem" href="#connpy.configfile.getitem">getitem</a></code></li>
<li><code><a title="connpy.configfile.getitems" href="#connpy.configfile.getitems">getitems</a></code></li> <li><code><a title="connpy.configfile.getitems" href="#connpy.configfile.getitems">getitems</a></code></li>
<li><code><a title="connpy.configfile.privatekey" href="#connpy.configfile.privatekey">privatekey</a></code></li>
<li><code><a title="connpy.configfile.publickey" href="#connpy.configfile.publickey">publickey</a></code></li>
</ul> </ul>
</li> </li>
<li> <li>