{"slug": "python-anti-slop-skill-md", "title": "Python Anti-Slop SKILL.md", "summary": "A developer published a Python coding guideline, \"anti-slop-python,\" that instructs engineers to strip defensive over-engineering from Python codebases. The rules target untyped dictionaries, generic wrapper helpers like to_string and as_dict, broad Any annotations, and blanket exception handling, arguing that validation should happen at system boundaries while internal logic relies on precise types and concrete library exceptions such as DuplicateKeyError.", "body_md": "| name | anti-slop-python | \n|---|---|\n| description | Simplify Python code by removing defensive over-engineering, unnecessary abstractions, generic dictionaries, excessive runtime checks, wrapper helpers, and AI-generated architectural noise. | \n\nApply these rules whenever modifying Python code.\n\nThe goal is simple, explicit, typed, idiomatic Python that is easy to trace and does not defend against impossible internal states.\n\nThe default philosophy is:\n\n**Validate untrusted data at the boundary. Use precise types and straightforward Python everywhere else.**\n\nPrefer boring code over clever abstractions.\n\nSearch aggressively for:\n\n```\nAny\ndict[str, Any]\nMapping[str, Any]\nlist[Any]\nobject\n```\n\ninside normal application logic.\n\nIf the shape is known, define it.\n\nBad:\n\n``` php\ndef get_domain(data: dict[str, Any]) -> str:\n    value = data.get(\"domain\")\n\n    if not isinstance(value, str):\n        return \"\"\n\n    return value\n```\n\nPrefer:\n\n```\n@dataclass\nclass DomainEvent:\n    domain: str\n```\n\nThen:\n\n```\nevent.domain\n```\n\nOr use the project's existing model system:\n\n```\nclass DomainEvent(BaseModel):\n    domain: str\n```\n\nDo not carry untyped dictionaries deep into the application.\n\nBe suspicious of functions such as:\n\n```\nas_string\nto_string\nsafe_string\nstring_value\nto_int\nas_int\nsafe_int\nto_bool\nto_dict\nas_dict\nensure_dict\nnormalize_value\n```\n\nBad:\n\n``` php\ndef to_string(value: Any) -> str:\n    if value is None:\n        return \"\"\n\n    if isinstance(value, str):\n        return value\n\n    if isinstance(value, int):\n        return str(value)\n\n    if isinstance(value, ObjectId):\n        return str(value)\n\n    return repr(value)\n```\n\nAsk:\n\nWhat is this value actually supposed to be?\n\nIf it is a string:\n\n``` php\ndef normalize_domain(domain: str) -> str:\n    return domain.strip().lower()\n```\n\nDo not make every function capable of accepting arbitrary Python values.\n\nBad:\n\n``` php\ndef normalize_domain(value: Any) -> str:\n    if isinstance(value, str):\n        return value.strip().lower()\n\n    if isinstance(value, int):\n        return str(value).strip().lower()\n\n    return \"\"\n```\n\nA domain name should not randomly be an integer.\n\nPrefer:\n\n``` php\ndef normalize_domain(domain: str) -> str:\n    return domain.strip().lower()\n```\n\nTypes should describe valid application states.\n\nDo not broaden inputs solely to make code more \"defensive.\"\n\nBad:\n\n``` php\ndef as_project(value: Any) -> Project:\n    if not isinstance(value, dict):\n        return {}\n\n    return cast(Project, value)\n```\n\nThis does not validate `Project`.\n\nEither validate at the boundary using the project's real validation mechanism, or trust the value at a known integration point.\n\nDo not write several runtime checks and still finish with `cast()`.\n\nDo not treat every `cast()` as inherently bad.\n\nBad cleanup:\n\n```\nif (\n    isinstance(error, Exception)\n    and hasattr(error, \"code\")\n    and isinstance(error.code, int)\n    and error.code == DUPLICATE_KEY_ERROR_CODE\n):\n    ...\n```\n\nwhen this is enough at a known driver boundary:\n\n```\nreturn cast(Any, error).code == DUPLICATE_KEY_ERROR_CODE\n```\n\nBetter still, use the driver's actual exception type if available:\n\n```\nexcept DuplicateKeyError:\n    ...\n```\n\nDo not turn one simple, understood assumption into five branches merely to avoid a cast.\n\nDo not manually inspect generic exceptions when the library exposes a concrete exception.\n\nBad:\n\n```\nexcept Exception as exc:\n    if getattr(exc, \"code\", None) == 11000:\n        ...\n```\n\nPrefer:\n\n```\nexcept DuplicateKeyError:\n    ...\n```\n\nUse exception APIs provided by the dependency.\n\nDo not reinvent error classification.\n\nBe suspicious of:\n\n```\ntry:\n    ...\nexcept Exception:\n    return None\n```\n\nor:\n\n```\ntry:\n    ...\nexcept Exception:\n    pass\n```\n\nor:\n\n```\ntry:\n    ...\nexcept Exception as exc:\n    logger.error(exc)\n    return {}\n```\n\nCatch exceptions you can actually handle.\n\nDo not silently convert unexpected programming errors into empty values.\n\nAvoid:\n\n```\ntry:\n    ...\nexcept:\n    pass\n```\n\nexcept at extremely deliberate process-level boundaries.\n\nBare `except` also catches system-exiting exceptions.\n\nUse specific exception types.\n\nBad:\n\n```\ntry:\n    return await service.run()\nexcept Exception:\n    raise\n```\n\nDelete the `try/except`.\n\nLikewise:\n\n```\ntry:\n    ...\nexcept Exception as exc:\n    raise exc\n```\n\nis usually worse than letting the exception propagate naturally.\n\nBad:\n\n```\ntry:\n    await operation()\nexcept Exception:\n    return None\n```\n\nIf failure is acceptable, make that business rule explicit.\n\nIf it is not acceptable, let the error propagate.\n\nSilent failure is not robustness.\n\nBad:\n\n```\ntry:\n    return repository.get_project(project_id)\nexcept Exception:\n    logger.exception(\"Failed to get project\")\n    raise\n```\n\nif an outer handler also logs the same error.\n\nPrefer returning/raising errors through internal layers and logging once at the responsible boundary.\n\nAvoid duplicate stack traces.\n\nSearch for suspicious patterns like:\n\n```\nreturn \"\"\nreturn {}\nreturn []\nreturn None\n\nvalue or \"\"\nvalue or {}\nvalue or []\n\ndata.get(\"field\", \"\")\ndata.get(\"field\", {})\n```\n\nwhen the fallback hides an invalid state.\n\nBad:\n\n```\nproject_id = data.get(\"project_id\", \"\")\n```\n\nif `project_id` is required.\n\nPrefer validation at the boundary and then:\n\n```\nproject_id = data.project_id\n```\n\nDo not hide missing required values.\n\nBad:\n\n```\nname = project.get(\"name\")\nregion = project.get(\"region\")\nconfig = project.get(\"config\", {})\n```\n\nwhen the schema guarantees those keys.\n\nIf it is a typed dictionary:\n\n```\nclass ProjectData(TypedDict):\n    name: str\n    region: str\n    config: ProjectConfig\n```\n\nprefer:\n\n```\nproject[\"name\"]\nproject[\"region\"]\nproject[\"config\"]\n```\n\nOr better, use a model/dataclass where appropriate.\n\nUse `.get()` when absence is actually valid.\n\nBad:\n\n```\ndomain = (\n    data.get(\"project\", {})\n    .get(\"config\", {})\n    .get(\"domain\", {})\n    .get(\"name\")\n)\n```\n\nThis usually hides malformed input.\n\nPrefer modeling the structure properly.\n\n```\ndata.project.config.domain.name\n```\n\nor validated dictionary access.\n\nDo not turn invalid nested structures into `None` silently.\n\nBe suspicious of:\n\n```\ndict[str, Any]\nMapping[str, Any]\nMutableMapping[str, Any]\n```\n\nmoving between:\n\n```\nhandler\n→ service\n→ manager\n→ repository\n→ processor\n```\n\nIf the schema is known, define a model.\n\nPossible tools include:\n\n- dataclasses\n- TypedDict\n- Pydantic models\n- attrs\n- domain classes\n\nUse whichever the project already uses.\n\nDo not introduce another modeling framework unnecessarily.\n\nGood:\n\n```\nclass QueuePayload(TypedDict):\n    event: str\n    data: dict[str, Any]\n```\n\ncan be appropriate at a JSON-shaped boundary.\n\nBut do not turn the entire domain model into nested TypedDicts if actual objects would make business logic clearer.\n\nUse the simplest representation that fits the project.\n\nDataclasses are useful when a plain object represents structured internal data.\n\nGood:\n\n```\n@dataclass(frozen=True)\nclass DomainMapEvent:\n    project_id: str\n    domain: str\n```\n\nAvoid adding:\n\n```\n__post_init__\nclassmethod factories\nbuilder methods\nconversion methods\nvalidation methods\n```\n\nunless they enforce meaningful invariants.\n\nA dataclass should not become a mini-framework.\n\nPydantic is excellent at boundaries.\n\nIt does not need to become the base representation for every internal object.\n\nUse it where validation/parsing is valuable:\n\n- HTTP requests\n- environment/config\n- external payloads\n- queue events\n- API responses\n\nInside trusted application code, plain dataclasses or typed objects may be simpler.\n\nFollow the codebase's existing style.\n\nBad:\n\n```\nrequest = ProjectRequest.model_validate(payload)\n\n...\n\nservice.create_project(request)\n\n...\n\nif not request.project_id:\n    raise ValueError(...)\n\n...\n\nif not isinstance(request.project_id, str):\n    ...\n```\n\nIf the boundary model already validated it, trust it.\n\nDo not revalidate the same object at every layer.\n\nBad:\n\n```\nif not isinstance(project.id, str):\n    return None\n```\n\nwhen:\n\n```\n@dataclass\nclass Project:\n    id: str\n```\n\nalready guarantees it.\n\nTrust internal types.\n\nIf the type is inaccurate, fix the type.\n\nBe suspicious of:\n\n```\nif hasattr(value, \"id\"):\n    ...\n```\n\nor:\n\n```\ndomain = getattr(document, \"domain\", None)\n```\n\nwhen the object has a known type.\n\nPrefer:\n\n```\ndocument.domain\n```\n\nUse `getattr` for truly dynamic APIs, not as general defensive programming.\n\nBad:\n\n```\nproject_id = getattr(project, \"id\", \"\")\n```\n\nif `project.id` is required.\n\nPrefer:\n\n```\nproject.id\n```\n\nIf the object may genuinely be absent:\n\n```\nif project is None:\n    raise ProjectNotFound(...)\n```\n\nThen continue normally.\n\nBe suspicious of:\n\n```\ngetattr\nsetattr\nhasattr\nvars\n__dict__\ninspect\ndir\n```\n\ninside ordinary business logic.\n\nPython is dynamic, but that does not mean application code should discover its own shape at runtime.\n\nUse explicit attributes and types.\n\nIf a feature relies heavily on:\n\n```\ninspect.signature\ninspect.getmembers\ninspect.isclass\n```\n\nask whether the code is building infrastructure/framework behavior or simply avoiding explicit APIs.\n\nReflection is appropriate in frameworks/tooling.\n\nIt is suspicious in normal domain logic.\n\nBad:\n\n``` php\ndef serialize_value(value: Any) -> str:\n    if isinstance(value, str):\n        return value\n    if isinstance(value, int):\n        return str(value)\n    if isinstance(value, ObjectId):\n        return str(value)\n    if isinstance(value, datetime):\n        return value.isoformat()\n    return repr(value)\n```\n\nunless arbitrary-value serialization is genuinely the feature.\n\nIf you know the field type, serialize that type directly.\n\nBe suspicious of:\n\n```\ndomain_id_from\nproject_id_from\nextract_id\nextract_name\nresolve_field\nsafe_field\nobject_id_from\nstring_from\n```\n\nBad:\n\n``` php\ndef domain_id_from(document: dict[str, Any] | None) -> str | None:\n    if document is None:\n        return None\n\n    domain = document.get(\"domain\")\n\n    if not isinstance(domain, ObjectId):\n        return None\n\n    return str(domain)\n```\n\nIf the document contract is known:\n\n```\n@dataclass\nclass DNSDocument:\n    domain: ObjectId\n```\n\nthen:\n\n```\nstr(document.domain)\n```\n\nFix typing rather than introducing another helper.\n\nBe suspicious of functions that:\n\n- have one caller\n- are one to three lines\n- only access a property\n- only call `.strip()`\n- only call `.lower()`\n- only perform `isinstance`\n- only return a fallback\n- only forward parameters\n- only rename another function\n\nBad:\n\n``` php\ndef get_project_id(project: Project) -> str:\n    return project.id\n```\n\nPrefer:\n\n```\nproject.id\n```\n\nHelpers should represent real concepts.\n\nDo not split simple logic into:\n\n```\nparser.py\nnormalizer.py\nvalidator.py\nconverter.py\nmapper.py\nresolver.py\nhelper.py\nprocessor.py\nmanager.py\n```\n\nfor a tiny feature.\n\nKeep related code together when it improves readability.\n\nSeparation of concerns does not mean separation of every statement.\n\nBe suspicious of:\n\n```\nController\n→ Service\n→ Manager\n→ Processor\n→ Handler\n→ Repository\n→ DAO\n```\n\nwhen most layers simply forward parameters.\n\nPython does not need enterprise ceremony.\n\nCollapse layers that add no logic.\n\nBad:\n\n``` php\nclass ProjectManager:\n    def get_project(self, project_id: str) -> Project:\n        return self.project_service.get_project(project_id)\n```\n\nand then:\n\n``` php\nclass ProjectService:\n    def get_project(self, project_id: str) -> Project:\n        return self.repository.get_project(project_id)\n```\n\nIf a layer adds no policy, transformation, caching, orchestration, or meaningful abstraction, remove it.\n\nBe suspicious of:\n\n```\nclass BaseService:\n    ...\n\nclass BaseRepository:\n    ...\n\nclass AbstractManager:\n    ...\n```\n\nwhen subclasses share little meaningful behavior.\n\nDo not create inheritance hierarchies just to centralize two utility methods.\n\nPrefer composition or direct code.\n\nBad:\n\n```\nclass MongoProjectRepository(BaseRepository, LoggingMixin, RetryMixin):\n    ...\n```\n\nwhen dependencies/functions can be explicit.\n\nMultiple inheritance and mixin stacks make behavior hard to trace.\n\nUse them only when they genuinely simplify the architecture.\n\nBe suspicious of:\n\n```\nLoggingMixin\nValidationMixin\nSerializationMixin\nRetryMixin\nTimestampMixin\nErrorHandlingMixin\n```\n\nfor ordinary application classes.\n\nMixins often hide dependencies and control flow.\n\nPrefer explicit calls or composition.\n\nDo not create:\n\n```\nclass ProjectRepository(Protocol):\n    ...\n\nclass AbstractProjectRepository(ABC):\n    ...\n```\n\nwhen there is one concrete implementation and no real abstraction need.\n\nProtocols/interfaces are useful for narrow consumer contracts and interchangeable implementations.\n\nDo not add them because \"good architecture requires interfaces.\"\n\nBad:\n\n``` python\nclass ProjectGetter(Protocol):\n    def get_project(...):\n        ...\n```\n\ncreated solely because one test needs a mock.\n\nPython's testing ecosystem already supports dependency substitution easily.\n\nUse protocols when they express a meaningful contract.\n\nBad:\n\n``` php\nclass RepositoryFactory:\n    def create(self, type_: str) -> Repository:\n        ...\n```\n\nwhen the application always uses one repository.\n\nPrefer direct construction.\n\nFactories should solve actual runtime selection or complex setup.\n\nBad:\n\n```\ndeployment = (\n    DeploymentBuilder()\n    .with_project_id(project_id)\n    .with_region(region)\n    .with_port(port)\n    .build()\n)\n```\n\nPrefer:\n\n```\ndeployment = Deployment(\n    project_id=project_id,\n    region=region,\n    port=port,\n)\n```\n\nPython already has excellent object construction syntax.\n\nDo not emulate Java builders unnecessarily.\n\nBe suspicious of:\n\n```\nProject.from_dict(...)\nProject.from_payload(...)\nProject.from_model(...)\nProject.from_entity(...)\nProject.from_record(...)\n```\n\nwhen the transformations are trivial or duplicate one another.\n\nUse alternative constructors only when they express genuinely different construction logic.\n\nAudit:\n\n```\nto_dict\nfrom_dict\nto_model\nfrom_model\nto_dto\nfrom_dto\nto_entity\nfrom_entity\nto_schema\nfrom_schema\n```\n\nIf two representations are nearly identical, question why both exist.\n\nDo not maintain fleets of copy-field transformations without a real boundary distinction.\n\nBe suspicious of:\n\n```\nProject\nProjectDTO\nProjectData\nProjectPayload\nProjectRequest\nProjectResponse\nProjectModel\nProjectEntity\nProjectRecord\n```\n\nwith almost the same fields.\n\nSeparate models where API/persistence/domain contracts genuinely differ.\n\nDo not duplicate types just because each layer supposedly needs its own model.\n\nPython dependency injection can simply be:\n\n```\nservice = ProjectService(repository, logger)\n```\n\nDo not introduce:\n\n```\nContainer\nRegistry\nProvider\nResolver\nServiceLocator\nDependencyGraph\n```\n\nwithout a real need.\n\nExplicit construction is easier to trace.\n\nBad:\n\n```\nrepo = services.get(\"project_repository\")\n```\n\nPrefer explicit dependencies.\n\nLikewise, avoid mutable module-level globals for application services/config where explicit wiring is practical.\n\nBad:\n\n```\nclass DatabaseSingleton:\n    _instance = None\n\n    @classmethod\n    def get_instance(cls):\n        ...\n```\n\nunless the lifecycle genuinely requires it.\n\nUsually the application startup layer can create one instance and pass it around.\n\nDo not add wrapper utilities around normal context manager behavior without value.\n\nBad:\n\n``` python\ndef safe_transaction(db):\n    return TransactionContext(db)\n```\n\nwhen:\n\n```\nwith db.transaction():\n    ...\n```\n\nalready expresses the operation clearly.\n\nDo use them for real resources:\n\n```\nwith open(path) as file:\nasync with client.stream(...) as response:\nwith transaction:\n```\n\nDo not replace appropriate resource management merely to reduce lines.\n\nBe suspicious of:\n\n``` python\n@retry(...)\ndef everything():\n```\n\nRetries are not generic safety.\n\nOnly retry operations that are:\n\n- transient\n- idempotent or safe to repeat\n- appropriate for retry semantics\n\nDo not add retries around arbitrary business logic.\n\nBe suspicious when functions accumulate:\n\n```\n@retry\n@log_execution\n@validate\n@measure\n@catch_errors\n@authorize\n@normalize\n```\n\nDecorators hide control flow.\n\nUse them for genuinely cross-cutting concerns with stable semantics.\n\nDo not turn basic logic into a decoration stack.\n\nBad:\n\n``` python\n@ensure_not_none\ndef process_project(...):\n```\n\nwhen:\n\n```\nif project is None:\n    raise ProjectNotFound(...)\n```\n\nis clearer.\n\nExplicit logic is often better than decorator magic.\n\nBad:\n\n```\nif project is not None:\n    if project.enabled:\n        if project.status == \"active\":\n            # 50 lines\n```\n\nPrefer:\n\n```\nif project is None:\n    raise ProjectNotFound(project_id)\n\nif not project.enabled:\n    return\n\nif project.status != \"active\":\n    return\n\n# main logic\n```\n\nKeep the happy path obvious.\n\nBad:\n\n```\nif enabled is True:\n```\n\nwhen:\n\n```\nif enabled:\n```\n\nis equivalent.\n\nBad:\n\n```\nif enabled == False:\n```\n\nPrefer:\n\n```\nif not enabled:\n```\n\nUse explicit `is True` only when tri-state behavior genuinely matters.\n\nBad:\n\n```\nvalue = \"a\" if active else \"b\" if enabled else \"c\"\n```\n\nPrefer normal control flow.\n\nDo not compress logic at the expense of readability.\n\nBad:\n\n```\nraw_domain = event.domain\ntrimmed_domain = raw_domain.strip()\nnormalized_domain = trimmed_domain.lower()\ndomain = normalized_domain\n```\n\nPrefer:\n\n```\ndomain = event.domain.strip().lower()\n```\n\nUse intermediate names only when they clarify meaningful concepts.\n\nBe suspicious of:\n\n```\nreturn list(items)\n```\n\nor:\n\n```\ncopy = items[:]\n```\n\nwhen ownership/mutation does not require a copy.\n\nDo not allocate defensively without a real reason.\n\nLikewise:\n\n```\nreturn dict(config)\n```\n\nshould have a concrete ownership reason.\n\nDo not copy mutable structures mechanically.\n\nSearch for:\n\n```\ncopy.deepcopy(...)\n```\n\nDeep copying can be expensive and usually signals unclear ownership.\n\nUse it only when nested mutation isolation is actually required.\n\nDo not add:\n\n```\n@lru_cache\n@cache\n```\n\nto functions without understanding:\n\n- lifecycle\n- cardinality\n- invalidation\n- memory growth\n- stale data behavior\n\nCaching is architecture, not a free optimization.\n\nDo not make functions async just because surrounding code is async.\n\nBad:\n\n``` php\nasync def normalize_domain(domain: str) -> str:\n    return domain.strip().lower()\n```\n\nPrefer synchronous functions for synchronous work.\n\nBe suspicious of:\n\n```\nasyncio.create_task(...)\n```\n\nadded simply to \"not block.\"\n\nEvery background task raises questions about:\n\n- ownership\n- cancellation\n- exception handling\n- shutdown\n- ordering\n- lifetime\n\nUse task creation deliberately.\n\nBad:\n\n```\nasyncio.create_task(send_event())\n```\n\nwith no task tracking or error handling.\n\nIf the result matters, await it.\n\nIf fire-and-forget is intentional, ensure the application owns the task lifecycle.\n\nDo not turn two trivial sequential calls into concurrency automatically.\n\nUse concurrent execution when operations are independent and actually benefit from overlapping I/O.\n\nDo not make control flow harder for theoretical speedups.\n\nAvoid:\n\n```\nasyncio.Lock()\nthreading.Lock()\n```\n\nwithout real shared mutable state and concurrency.\n\nLocks create lifecycle and deadlock complexity.\n\nProtect actual races, not hypothetical ones.\n\nDo not wrap already-async libraries in:\n\n```\nasyncio.to_thread(...)\nrun_in_executor(...)\n```\n\nwithout need.\n\nUse thread offloading for genuinely blocking operations.\n\nDo not introduce workers/process pools for small CPU work without evidence.\n\nMeasure first.\n\nSimple code first.\n\nBe suspicious of:\n\n```\n@dataclass\nclass Result(Generic[T]):\n    value: T | None\n    error: Exception | None\n    success: bool\n```\n\nPython already has exceptions.\n\nDo not emulate Rust/Go-style result handling unless the codebase deliberately uses that model.\n\nQuestion:\n\n```\nstr | None\nProject | None\nConfig | None\n```\n\nwhen the value is actually required after construction.\n\nDo not model required internal state as nullable purely because data initially enters incompletely.\n\nParse/build a valid object first.\n\nBad:\n\n```\nif project is None:\n    return None\n\nif project.config is None:\n    return None\n\nif project.config.domain is None:\n    return None\n```\n\nwhen the object contract says these are required.\n\nFix the model.\n\nUse `None` only for real optionality.\n\nBad:\n\n```\nport = value or 8080\n```\n\nwhen `0` might have meaning.\n\nBad:\n\n```\nenabled = value or True\n```\n\nUse explicit `None` handling when appropriate:\n\n```\nport = 8080 if value is None else value\n```\n\nDo not conflate falsey with missing.\n\nBe suspicious of:\n\n```\nstr(value)\nint(value)\nbool(value)\nfloat(value)\n```\n\nused as \"validation.\"\n\nFor example:\n\n```\nbool(\"false\")\n```\n\nis `True`.\n\nDo not silently coerce malformed external data.\n\nParse it according to its real contract.\n\nGood:\n\n```\nclass DeploymentStatus(StrEnum):\n    PENDING = \"pending\"\n    RUNNING = \"running\"\n    FAILED = \"failed\"\n```\n\nwhen the valid states are closed and domain-significant.\n\nDo not create an enum for every arbitrary string.\n\nBe suspicious of:\n\n```\n@dataclass\nclass ProjectID:\n    value: str\n```\n\nwhen a string is sufficient.\n\nA custom type can be useful when it provides real validation or domain behavior.\n\nDo not wrap every primitive.\n\nBad:\n\n``` python\nclass ProjectList:\n    def __init__(self, projects: list[Project]):\n        self._projects = projects\n```\n\nwith methods that merely proxy list behavior.\n\nUse built-in collections unless a domain abstraction provides real value.\n\nBefore keeping custom helpers, check whether Python already has the operation.\n\nPrefer:\n\n```\nstr.strip\nstr.lower\npathlib.Path\ncollections.defaultdict\nitertools\nfunctools\ndataclasses\nenum\ncontextlib\nurllib.parse\n```\n\nwhere appropriate.\n\nDo not maintain custom versions of standard behavior.\n\nAudit modules/packages called:\n\n```\nutils.py\nhelpers.py\ncommon.py\nshared.py\nmisc.py\nbase.py\ncore.py\n```\n\nThese often collect unrelated functions.\n\nDelete trivial helpers.\n\nMove domain-specific logic to the domain that owns it.\n\nDo not create another generic utility module during cleanup.\n\nDo not create a new file for every class/function.\n\nBad:\n\n```\ndomain_parser.py\ndomain_normalizer.py\ndomain_validator.py\ndomain_converter.py\n```\n\nfor four tiny functions.\n\nGroup cohesive functionality.\n\nFile count is not architecture quality.\n\nA module can contain several closely related functions.\n\nDo not interpret \"single responsibility\" as \"one function per file.\"\n\nOptimize for discoverability.\n\nBe suspicious of:\n\n```\nHANDLERS = {}\nregister_handler(...)\nregister_service(...)\nPLUGIN_REGISTRY = {}\n```\n\nwhen a normal `match`/dictionary literal/import is sufficient.\n\nUse dynamic registration only when extensibility is genuinely required.\n\nBad:\n\n```\nhandler = registry.resolve(event.type)\nreturn handler.process(event)\n```\n\nwhen:\n\n```\nmatch event.type:\n    case \"insert\":\n        return handle_insert(event)\n    case \"delete\":\n        return handle_delete(event)\n```\n\nis clearer.\n\nDo not turn static cases into plugin architecture.\n\nThree branches do not automatically need:\n\n```\nBaseStrategy\nInsertStrategy\nDeleteStrategy\nReplaceStrategy\nStrategyFactory\n```\n\nUse ordinary Python control flow when easier to understand.\n\nBad:\n\n```\nprocessor(value, lambda x: normalize(x))\n```\n\nwhen:\n\n```\nprocessor(value, normalize)\n```\n\nis enough.\n\nOr simply:\n\n```\nnormalize(value)\n```\n\nif the abstraction itself is unnecessary.\n\nDo not turn complex business logic into dense comprehensions.\n\nBad:\n\n```\nresult = {\n    x.id: transform(x)\n    for x in items\n    if x.enabled and x.config and x.config.valid\n}\n```\n\nwhen a loop would make failure cases and rules clearer.\n\nComprehensions are for simple transformations.\n\nBad:\n\n```\nreturn next((x for x in values if x.id == id_), None)\n```\n\nis fine when obvious.\n\nBut do not compress multi-step business logic into nested expressions simply to save lines.\n\nReadability first.\n\nBe suspicious of:\n\n```\nmap(...)\nfilter(...)\nreduce(...)\npartial(...)\n```\n\nwhen a simple loop is clearer.\n\nPython often reads better with comprehensions or direct loops.\n\nDo not optimize for abstract functional style.\n\nBad:\n\n```\nlist(map(lambda x: x.id, projects))\n```\n\nPrefer:\n\n```\n[project.id for project in projects]\n```\n\nUse idiomatic Python.\n\nBad:\n\n``` php\ndef is_empty(value: str) -> bool:\n    return len(value) == 0\n```\n\nPrefer:\n\n```\nif not value:\n```\n\nwhen semantics match.\n\nDo not wrap obvious built-ins for no reason.\n\nBad:\n\n```\nre.sub(r\"^\\s+|\\s+$\", \"\", domain)\n```\n\nPrefer:\n\n```\ndomain.strip()\n```\n\nUse regex for regex-shaped problems.\n\nDo not use dynamic code execution to solve configuration, expression, or dispatch problems unless the feature explicitly requires it and security implications are understood.\n\nPrefer explicit parsing.\n\nDo not patch classes/functions at runtime to avoid proper dependency design.\n\nMonkey patching may be appropriate in tests or specialized libraries.\n\nIt should not be normal application architecture.\n\nMetaclasses are rarely required in ordinary application code.\n\nBe highly suspicious of introducing:\n\n```\nclass FooMeta(type):\n    ...\n```\n\nfor registration, validation, or convenience.\n\nPrefer normal classes/decorators/functions.\n\nLikewise, do not introduce custom descriptors for basic validation or computed fields when properties/dataclasses/models solve the problem more clearly.\n\nBad:\n\n``` php\nclass Project:\n    @property\n    def id(self) -> str:\n        return self._id\n```\n\nwhen there is no invariant or encapsulation need.\n\nUse plain attributes where appropriate.\n\nPython does not need Java-style getters/setters.\n\nBad:\n\n```\nproject.get_id()\nproject.set_id(id_)\n```\n\nfor ordinary attributes.\n\nPrefer:\n\n```\nproject.id\n```\n\nunless access has real behavior.\n\nDo not use:\n\n```\nself._project_id\n```\n\nplus a property purely for encapsulation theater.\n\nPython's conventions are enough.\n\nUse private-ish fields when they represent internal implementation state.\n\nFor Mongo/PyMongo:\n\nBad:\n\n```\ndocument: dict[str, Any]\ndomain = document.get(\"domain\")\n\nif isinstance(domain, ObjectId):\n    return str(domain)\n```\n\nwhen the document schema is known.\n\nUse a model/TypedDict:\n\n```\nclass DNSDocument(TypedDict):\n    domain: ObjectId\n```\n\nthen:\n\n```\nstr(document[\"domain\"])\n```\n\nOr use an ODM model if the project already does.\n\nIf SQLAlchemy/Django models define:\n\n```\nproject.id: str\nproject.enabled: bool\n```\n\ndo not repeatedly runtime-check them inside business logic.\n\nTrust the ORM model unless the field genuinely allows null.\n\nBad:\n\n```\nrepository.find(\n    collection=\"projects\",\n    filters={\"id\": project_id},\n    projection=None,\n    options={}\n)\n```\n\nwhen the application has a clear domain operation.\n\nPrefer:\n\n```\nproject_repository.get(project_id)\n```\n\nDo not make internal APIs mimic a generic database driver unless generic behavior is genuinely needed.\n\nAvoid:\n\n```\nRepository\n→ DAO\n→ Store\n→ DatabaseClient\n→ Session\n```\n\nfor simple persistence.\n\nUse the minimum layering that gives useful testability and separation.\n\nDo not build elaborate:\n\n```\nUnitOfWork\nTransactionManager\nTransactionScope\nTransactionProvider\n```\n\naround a small amount of transaction code unless the application genuinely benefits.\n\nUse the ORM/database's native transaction API directly where clearer.\n\nBad:\n\n```\nclass QueueMessage:\n    event: str\n    data: dict[str, Any]\n```\n\nwith downstream code doing:\n\n```\nif message.event == \"domain.map\":\n    data = as_domain_map_event(message.data)\n```\n\nPrefer event-specific parsing at the boundary.\n\nExample:\n\n```\nclass DomainMapEvent(BaseModel):\n    event: Literal[\"domain.map\"]\n    project_id: str\n    domain: str\n```\n\nOr use discriminated unions if the project's validation library supports them.\n\nParse JSON once.\n\nValidate once.\n\nConvert into the domain representation once.\n\nDo not repeatedly do:\n\n```\njson.loads(...)\ndict(...)\nmodel_validate(...)\nasdict(...)\n```\n\nacross layers.\n\nBad:\n\n```\ndata = json.loads(model.model_dump_json())\n```\n\nor:\n\n```\npayload = json.loads(json.dumps(data))\n```\n\njust to convert structures.\n\nUse direct conversions or actual typed objects.\n\nWith Pydantic, do not constantly turn models back into raw dicts:\n\n```\nservice.run(payload.model_dump())\n```\n\nif the service could accept the model/type directly.\n\nDump only at serialization or integration boundaries.\n\nBad:\n\n```\nservice.create_project({\n    \"project_id\": project_id,\n    \"region\": region,\n})\n```\n\nwhen:\n\n```\nservice.create_project(project_id, region)\n```\n\nor a meaningful request object is clearer.\n\nUse parameter objects when the values form a real concept or the parameter list is substantial.\n\nDo not pass huge context objects where only two fields are needed.\n\nBad:\n\n``` php\ndef sync_project(payload: ProjectPayload) -> None:\n    project_id = payload.project_id\n    rate_limit = payload.rate_limit\n```\n\nif only those fields matter.\n\nPrefer:\n\n``` php\ndef sync_project(project_id: str, rate_limit: int) -> None:\n```\n\nunless the payload itself is the meaningful domain concept.\n\nBad:\n\n``` php\ndef create_project(options: dict[str, Any]) -> Project:\n```\n\nPrefer explicit parameters or a concrete model.\n\nGeneric option dictionaries push validation problems downstream.\n\nBad:\n\n```\ndeploy(\n    project,\n    force=True,\n    skip_cache=False,\n    async_mode=True,\n    validate=False,\n)\n```\n\nIf flags create meaningfully different operations, consider separate functions or a clear options model.\n\nDo not create cryptic combinations of booleans.\n\nAt the same time, do not turn:\n\n```\ndeploy(project_id)\n```\n\ninto:\n\n```\nDeployCommand(\n    options=DeployOptions(\n        behavior=DeployBehavior(...)\n    )\n)\n```\n\nwithout a real reason.\n\nAvoid both extremes.\n\nAvoid:\n\n```\nlogger.debug(\"Entering function\")\nlogger.debug(\"Validating input\")\nlogger.debug(\"Calling repository\")\nlogger.debug(\"Repository returned\")\nlogger.debug(\"Returning result\")\n```\n\nLog:\n\n- failures\n- important state transitions\n- external operations\n- debugging information with real operational value\n\nDo not narrate code execution.\n\nIf the codebase uses structured logging:\n\n```\nlogger.info(\n    \"project deployed\",\n    extra={\"project_id\": project_id},\n)\n```\n\nfollow existing conventions.\n\nDo not introduce a new logging framework during cleanup.\n\nDelete comments like:\n\n```\n# Check if project exists\nif project is None:\n# Convert domain to lowercase\ndomain = domain.lower()\n```\n\nKeep comments for:\n\n- business rules\n- quirks\n- invariants\n- external constraints\n- non-obvious reasoning\n\nComments should explain why.\n\nBad:\n\n``` php\ndef get_project(project_id: str) -> Project:\n    \"\"\"Get a project.\"\"\"\n```\n\nThis adds nothing.\n\nKeep docstrings for public APIs, complex semantics, parameters with non-obvious contracts, or behavior worth documenting.\n\nDo not add huge Google/Numpy-style docstrings to obvious private helpers.\n\nExample of unnecessary ceremony:\n\n``` php\ndef normalize_domain(domain: str) -> str:\n    \"\"\"\n    Normalize the domain name.\n\n    Args:\n        domain: The domain name.\n\n    Returns:\n        The normalized domain name.\n    \"\"\"\n```\n\nPrefer self-explanatory code.\n\nApply anti-slop rules to tests too.\n\nBe suspicious of:\n\n```\nTestDataBuilder\nMockFactory\nFixtureFactory\nScenarioBuilder\nBaseTestCase\nIntegrationTestHelper\n```\n\nfor simple tests.\n\nPrefer direct setup and pytest fixtures where useful.\n\nDo not mock pure internal code unnecessarily.\n\nMock real external boundaries where isolation matters:\n\n- network\n- filesystem\n- third-party APIs\n- expensive infrastructure\n\nUse real domain objects for internal logic when practical.\n\nGood:\n\n```\n@pytest.mark.parametrize(\n    (\"value\", \"expected\"),\n    [\n        (\"EXAMPLE.COM\", \"example.com\"),\n        (\" example.com \", \"example.com\"),\n    ],\n)\ndef test_normalize_domain(value: str, expected: str) -> None:\n    assert normalize_domain(value) == expected\n```\n\nDo not build a testing DSL for three cases.\n\nDo not create:\n\n```\nBaseServiceTest\nBaseRepositoryTest\nBaseIntegrationTest\n```\n\nunless there is substantial common lifecycle behavior.\n\nPrefer fixtures and composition.\n\nBad:\n\n``` python\ndef safe_mock_return(mock: Mock, default=None):\n```\n\nUse the mocking library normally.\n\nDo not invent abstraction around standard test tooling without need.\n\nDo not remove validation for:\n\n- HTTP requests\n- CLI arguments\n- queue payloads\n- config/env vars\n- webhooks\n- untrusted JSON\n- external APIs\n- schemaless database records\n- user input\n- file contents\n\nThat is where defensive programming belongs.\n\nDo not remove:\n\n- network timeout handling\n- database errors\n- not-found cases\n- transaction rollback\n- file-not-found handling\n- permission errors\n- subprocess failures\n- cancellation handling\n- API-specific exceptions\n- security checks\n\nThis is not a request to make code fragile.\n\nThe distinction is:\n\n**Defend against external uncertainty, not against correctly typed internal code.**\n\nThis is not a cleanup:\n\n```\nvalue = cast(str, data[\"domain\"])\n```\n\nbecoming:\n\n```\nvalue = data.get(\"domain\")\n\nif value is None:\n    return \"\"\n\nif not isinstance(value, str):\n    return \"\"\n\nif len(value) == 0:\n    return \"\"\n\nreturn value\n```\n\nif the actual contract says `domain` is required and is a string.\n\nThe correct fix is:\n\n```\n@dataclass\nclass DomainEvent:\n    domain: str\n```\n\nand then:\n\n```\nevent.domain\n```\n\nWhenever you see:\n\n```\nsafe_x\nas_x\nnormalize_x\nextract_x\nconvert_x\nresolve_x\nensure_x\ncoerce_x\n```\n\ntrace the value upstream.\n\nAsk:\n\n1. Why is this value not already typed?\n2. Where does it enter the application?\n3. Is that where validation belongs?\n4. Can downstream code receive a concrete type?\n5. Can the helper disappear entirely?\n\nAlways prefer fixing the earliest sensible point in the data flow.\n\nSearch the repository for:\n\n```\nAny\ndict[str, Any]\nMapping[str, Any]\nobject\ncast(\nisinstance(\nhasattr(\ngetattr(\nsetattr(\nvars(\n__dict__\ninspect.\ntype(\ntry:\nexcept Exception\nexcept:\npass\nreturn None\nreturn {}\nreturn []\nreturn \"\"\n.get(\nor {}\nor []\nor \"\"\ndeepcopy\nBase\nAbstract\nMixin\nManager\nProcessor\nFactory\nBuilder\nResolver\nConverter\nMapper\nValidator\nHelper\nUtils\nProtocol\nABC\nGeneric\nTypeVar\ncreate_task\ngather\nto_thread\nrun_in_executor\nretry\nlru_cache\n```\n\nDo not automatically remove every match.\n\nUse them as signals to inspect for unnecessary complexity.\n\nEspecially review directories/features containing combinations like:\n\n```\nbase.py\ninterfaces.py\nprotocols.py\nfactory.py\nbuilder.py\nmapper.py\nconverter.py\nvalidator.py\nresolver.py\nmanager.py\nprocessor.py\nhelpers.py\nutils.py\nservice.py\nrepository.py\n```\n\nfor one small feature.\n\nDetermine whether each layer has real behavior.\n\nCollapse meaningless ones.\n\nBefore adding or keeping code, ask:\n\nIs this defending against something that can genuinely happen here?\n\nIf no, remove it.\n\nAsk:\n\nIs this complexity caused by an overly broad type?\n\nIf yes, fix the type.\n\nAsk:\n\nDoes this helper represent a real concept?\n\nIf no, inline it.\n\nAsk:\n\nDoes this abstraction reduce total complexity?\n\nIf no, delete it.\n\nAsk:\n\nIs this dynamic Python because the problem is actually dynamic, or because the code does not know its own types?\n\nIf the latter, fix the model.\n\nAsk:\n\nWould plain Python be easier to understand?\n\nIf yes, use plain Python.\n\nPrefer:\n\n``` php\nasync def handle_domain_map(\n    event: DomainMapEvent,\n) -> None:\n    domain = event.domain.strip().lower()\n\n    await domain_service.map(\n        project_id=event.project_id,\n        domain=domain,\n    )\n```\n\nover:\n\n``` php\nasync def handle_domain_map(\n    raw_data: Any,\n) -> None:\n    data = ensure_dict(raw_data)\n\n    project_id = safe_string(\n        data.get(\"project_id\")\n    )\n\n    domain = normalize_value(\n        data.get(\"domain\")\n    )\n\n    if not project_id or not domain:\n        return\n\n    payload = DomainMappingPayload.from_dict(\n        {\n            \"project_id\": project_id,\n            \"domain\": domain,\n        }\n    )\n\n    await domain_manager.process_mapping(payload)\n```\n\nBefore editing Python code:\n\n1. Identify the trusted and untrusted boundaries.\n2. Determine the actual data shapes.\n3. Check whether `Any` /generic dictionaries are necessary.\n4. Understand whether `None` is genuinely valid.\n5. Inspect whether abstractions have real callers/implementations.\n6. Prefer fixing data models upstream instead of adding local guards.\n7. Do not add new helpers before understanding why the existing type is broad.\n\nBefore finishing any Python change, inspect the diff.\n\nFor every added:\n\n- helper\n- class\n- protocol\n- base class\n- mixin\n- factory\n- builder\n- wrapper\n- fallback\n- `isinstance`\n- `getattr`\n- `Any`\n- `dict[str, Any]`\n- `try/except`\n- nullable type\n- decorator\n\nask:\n\n1. Does this handle a state that can genuinely occur?\n2. Am I compensating for bad typing upstream?\n3. Could this code be direct instead?\n4. Did I add another abstraction layer?\n5. Does this helper have a real reusable concept?\n6. Am I hiding invalid input behind an empty value?\n7. Am I swallowing an error?\n8. Did I introduce dynamic behavior where the shape is known?\n9. Did the change increase total complexity?\n10. Could any newly added code simply be deleted?\n\nIf yes, simplify before completing the task.\n\nThe codebase should trend toward:\n\n- fewer `Any` values\n- fewer generic dictionaries\n- fewer runtime type checks\n- fewer `.get()` chains\n- fewer silent fallbacks\n- fewer broad `except Exception`\n- fewer tiny helper functions\n- fewer mapper/converter classes\n- fewer base classes and mixins\n- fewer factories/builders\n- fewer unnecessary protocols\n- fewer pass-through layers\n- more precise models\n- validation concentrated at boundaries\n- direct attribute access\n- explicit business logic\n- idiomatic Python\n- easy-to-follow control flow\n\nThe important metric is not line count alone.\n\nThe important metric is:\n\n**Can another engineer understand the behavior without navigating defensive machinery and unnecessary abstractions?**\n\nDo not transform:\n\n```\nvalidated typed input\n→ direct business logic\n```\n\ninto:\n\n```\nAny\n→ isinstance\n→ getattr\n→ safe helper\n→ converter\n→ fallback\n→ wrapper\n→ manager\n→ actual operation\n```\n\nThe desired flow is:\n\n```\nuntrusted input\n→ parse/validate once\n→ precise Python type\n→ straightforward business logic\n```\n\nThe overriding principle is:\n\n**Make uncertainty explicit at the boundary. Keep trusted Python code simple, typed, direct, and boring.**", "url": "https://wpnews.pro/news/python-anti-slop-skill-md", "canonical_source": "https://gist.github.com/pipethedev/5707c47edd0d6994347adef72c533de9", "published_at": "2026-09-10 10:50:25+00:00", "updated_at": "2026-09-10 11:57:31.284909+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["Python"], "alternates": {"html": "https://wpnews.pro/news/python-anti-slop-skill-md", "markdown": "https://wpnews.pro/news/python-anti-slop-skill-md.md", "text": "https://wpnews.pro/news/python-anti-slop-skill-md.txt", "jsonld": "https://wpnews.pro/news/python-anti-slop-skill-md.jsonld"}}