Part 3: OOP, Dataclasses and Protocols
Introduction
Classes and __init__
__init__class ProfileConverter:
"""Converts an InSpec profile directory into an Ansible collection."""
# Class variable — shared across all instances
supported_resources: list[str] = [
"file", "service", "package",
"security_policy", "registry_key", "audit_policy",
]
def __init__(self, profile_path: str, namespace: str = "local") -> None:
self.profile_path = profile_path
self.namespace = namespace
self._controls: list[dict] = [] # private by convention
def load(self) -> None:
"""Parse controls from the profile directory."""
import os
for root, _, files in os.walk(self.profile_path):
for f in files:
if f.endswith(".rb"):
self._parse_file(os.path.join(root, f))
def _parse_file(self, path: str) -> None:
"""Internal — parse a single .rb control file."""
with open(path) as f:
content = f.read()
# Simplified: real impl does Ruby AST parsing
self._controls.append({"path": path, "raw": content})
def convert(self) -> dict:
"""Return the Ansible collection structure as a dict."""
if not self._controls:
self.load()
return {
"namespace": self.namespace,
"controls": self._controls,
}Inheritance
super() — calling parent methods
super() — calling parent methods@dataclass
@dataclass@dataclass(frozen=True) — immutable data
@dataclass(frozen=True) — immutable data@dataclass vs Pydantic
@dataclass vs PydanticProtocol — Structural Subtyping
Protocol — Structural SubtypingAbstract Base Classes (ABC)
ABC vs Protocol
Class Methods and Static Methods
Properties
Putting It Together
Summary
Concept
When to use
What's Next
Last updated