بنقرة واحدة
python-code-style
Python code conventions. Functions return errors, .env parsing strips quotes, type hints required.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Python code conventions. Functions return errors, .env parsing strips quotes, type hints required.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
Project architecture and design principles for vm_builds Ansible project. Includes bake vs configure patterns, two-role service model, and deployment lifecycle.
Proactive validation patterns to catch issues early, prevent debugging blind, and establish testing habits before problems escalate.
Update skills and rules when encountering new issues to prevent recurrence. Includes hard-fail patterns, code custodianship, and mandatory testing requirements.
LXC container provisioning and configuration patterns. Use when creating LXC services, managing container networking, or handling LXC template operations.
Molecule test performance optimization patterns. Template caching, NTP sync, pct_remote overhead, apt cache, selective rebuilds.
Molecule testing patterns, TDD workflow, baseline management, and scenario architecture. Use when running tests, managing test workflows, or setting up molecule scenarios.
| name | python-code-style |
| description | Python code conventions. Functions return errors, .env parsing strips quotes, type hints required. |
def find_ansible_playbook(playbook: str) -> str:
if not found:
raise FileNotFoundError(f"Playbook not found: {playbook}")
return path
def get_cached_address() -> str | None:
path = Path('.state/addresses.json')
if not path.exists():
return None
return json.loads(path.read_text()).get('PRIMARY_HOST')
def probe_host(host: str, port: int, timeout: int = 5) -> bool:
try:
with socket.create_connection((host, port), timeout):
return True
except (socket.timeout, ConnectionRefusedError):
return False
value = value.strip()
if len(value) >= 2 and value[0] == value[-1] and value[0] in ('"', "'"):
value = value[1:-1]
env[key.strip()] = value
def ensure_file_exists(path: Path) -> Path:
path = path.expanduser().resolve()
if not path.is_file():
raise FileNotFoundError(f"Not a file: {path}")
return path
def ensure_dir(path: Path) -> Path:
path.mkdir(parents=True, exist_ok=True)
return path
def merge_dicts(*dicts: dict) -> dict:
result = {}
for d in dicts:
result.update(d)
return result
config = merge_dicts(default_config, env_config, cli_config)
import logging
log = logging.getLogger(__name__)
log.debug("Probing host %s", host) # Use % formatting
msg = f"Built command for {playbook}" # OK for non-logging
ALWAYS separate business logic from UI rendering using domain classes in the data layer. Domain objects own derived state — the UI only reads properties.
# Domain class in data.py — owns identity, deploy history, AND live telemetry
class Host:
def __init__(self, name: str, ip: str, *, is_lan: bool = False) -> None:
self.name = name
self.ip = ip
self.is_lan = is_lan
self.deploys: list[DeployRecord] = []
self.telemetry: HostTelemetry | None = None
def attach_telemetry(self, t: HostTelemetry) -> None:
self.telemetry = t
self._guests = _parse_guests(t.services)
@property
def healthy(self) -> bool: ... # from deploy history
@property
def errors(self) -> list[str]: ... # deploy + offline errors
@property
def online(self) -> bool: ... # from telemetry (False if None)
@property
def disk_pct(self) -> float: ... # 0.0 if no telemetry
@property
def guests(self) -> list[GuestInfo]: ... # [] if no telemetry
# Aggregate class — delegates to children, adds fleet-wide metrics
class Fleet:
def __init__(self, hosts: list[Host]) -> None:
self.hosts = hosts
@property
def healthy(self) -> bool: ...
@property
def online_count(self) -> int: ...
@property
def total_guests(self) -> int: ...
@property
def health_score(self) -> int: ... # 0-100
@property
def worst_disk(self) -> Host | None: ...
def get_host(self, name: str) -> Host | None: ...
# Factory function — wires ALL data sources into domain objects
def build_fleet(env: dict[str, str], state_dir: Path) -> Fleet:
hosts = [Host(name=i.name, ip=i.ip, ...) for i in get_known_hosts(env)]
for record in load_deploy_history(state_dir):
for host in hosts:
if _deploy_targets_host(record, host.name):
host.deploys.append(record)
for node in load_node_registry(state_dir):
host = ... # match by hostname
if host:
host.attach_telemetry(HostTelemetry(...))
return Fleet(hosts)
# UI page — thin renderer, ZERO business logic
def _deploy_card(fleet: Fleet) -> None:
if fleet.healthy:
ui.badge("success", color="green")
else:
for err in fleet.errors:
ui.label(err)
Rules:
.healthy / .errors properties.data.py..healthy -> bool and .errors -> list[str] on domain objects.telemetry is None (0.0, [], False, "--").RegisteredNode directly in page modules — use Host/Fleet.# ALWAYS centralize UI strings as class constants in data.py
class Labels:
START_DEPLOY = "Start Deploy" # used by both page module AND tests
# Page module:
ui.button(Labels.START_DEPLOY, ...)
# Test:
user.find(Labels.START_DEPLOY).click()
# NEVER duplicate strings across page + test:
# BAD: ui.button("Start Deploy") + user.find("Start Deploy")
# GOOD: both reference Labels.START_DEPLOY
except Exception in UI code caught too broadly. Use specific exceptions.format_last_seen_relative crashed on timezone-aware timestamps. Fix: .replace(tzinfo=None).data.py
constants (Routes, PageTitles, Labels, ApiRoutes).Host/Fleet domain classes into data.py with .healthy/.errors
properties. Dashboard became a thin renderer. 29 pure-Python tests cover all
health scenarios."Host" forward reference in kickstart_callhome(host: "Host") when
from __future__ import annotations is already at the top. With postponed
annotations, all annotations are strings by default — quoting is unnecessary.kickstart_callhome returned success=True even with partial container restart
failures (errors collected in errors list). This is intentional — success
means "SSH connected and discovered containers"; errors captures per-container
issues. Document this semantic in the docstring when success has a nuanced
meaning.HostRegistry.register() is the single write path to registry.json. NEVER
duplicate upsert logic. Every caller (env seeding, manual form, TEST_UNITS)
delegates to register(). Immutable-on-create fields (bucket, source,
first_seen) are preserved on upsert. MAC match takes precedence over name
match for identity resolution.nodes.json (telemetry) via register_checkin(). build_fleet() groups
container telemetry under parent hosts — containers never appear as
independent fleet members. Previous bug: register_checkin() called
HostRegistry.register() for every heartbeat. Containers (pihole,
wireguard, netdata, etc.) appeared as independent hosts on the dashboard
with LAN IPs (10.10.10.x) instead of being grouped under their physical
host. Fixed by removing the HostRegistry.register() call from
register_checkin()./api/checkin
in the final architecture.WEBUI_PORT env var (default 40500). Ports in the
ephemeral range (32768-60999) can collide with outbound connections. Choose a
port in the firewall's allowed range and set it in .env/test.env.build.py:get_controller_ip(). Import from build module..state/deploy_output.log), not just
stream to a NiceGUI ui.log element. Browser disconnects kill the coroutine
and lose all output. The log file is the source of truth.log.push, label.text = ...) throw RuntimeError when
the browser session is gone. Use a _safe_ui(fn, *args) helper to wrap these
in a single try/except RuntimeError. NEVER scatter bare try/except blocks.Labels in a page module, ALWAYS use the constants for button text
instead of hardcoding strings. If you import Labels but use hardcoded strings,
the import is dead AND the string is untracked. Previous bug: bridge.py and
images.py imported Labels but used hardcoded "Refresh Now", "Build Selected",
etc. — found by dead-import audit, fixed by replacing strings with constants.Labels.REFRESH_NOW,
import Labels AND use it. If it doesn't need Labels, don't import it.