Skip to content

.pr_agent_auto_best_practices

qodo-merge-bot edited this page Jun 18, 2026 · 10 revisions

Pattern 1: When implementing an apply()-style upsert, only fall back to create() on a true "not found" condition (HTTP 404 / explicit NotFound), and re-raise all other errors (403/500/etc) to avoid masking real failures.

Example code before:

def apply(self, name, body):
  try:
    self.find(name)
    return self.update(name, body)
  except DuploError:
    return self.create(body)

Example code after:

def apply(self, name, body):
  try:
    self.find(name)
    return self.update(name, body)
  except DuploError as e:
    if getattr(e, "code", None) == 404:
      return self.create(body)
    raise
Relevant past accepted suggestions:
Suggestion 1: [reliability] `apply` catches all `DuploError`
`apply` catches all `DuploError` `apply()` falls back to `create()` for any `DuploError` thrown by `find()`/`update()`, which can mask real failures like 403/500. Only the intended not-found condition (e.g., 404) should trigger create; other errors should be re-raised.

Issue description

aws_secret.apply() catches all DuploError exceptions and falls back to create(), which can hide authorization/server errors and perform unintended creates.

Issue Context

Compliance requires fallback behavior only for the intended condition (typically 404 Not Found). Other status codes must be re-raised.

Fix Focus Areas

  • src/duplo_resource/aws_secret.py[221-225]

Suggestion 2: [correctness] Apply misses 404 errors
Apply misses 404 errors `DuploResourceV2.apply()`/`DuploResourceV3.apply()` now catch only `DuploNotFound`, so resources whose `find()` raises `DuploError(..., 404)` will cause `apply()` to error instead of falling back to `create()`. This breaks `apply` for resources like `batch_queue` (custom `find`) and `ecr` (delegates not-found to `cloud_resource.find()` which raises `DuploError(404)`).

Issue description

DuploResourceV2.apply() and DuploResourceV3.apply() now catch only DuploNotFound. Many resources still raise DuploError(..., 404) from custom find() implementations, so apply() no longer falls back to create() for them.

Issue Context

Examples include batch_queue.find() and cloud_resource.find() (used by ecr.find()), both raising DuploError(404).

Fix Focus Areas

  • src/duplocloud/resource.py[143-152]
  • src/duplocloud/resource.py[335-340]
  • src/duplo_resource/batch_queue.py[48-70]
  • src/duplo_resource/cloud_resource.py[63-95]
  • src/duplo_resource/batch_job.py[59-84]

Notes

Preferred approach: in both apply methods catch DuploError as e and only create when e.code == 404 (re-raise otherwise). Optionally, migrate custom find() methods to raise DuploNotFound(name, kind) for consistency.


Suggestion 3: [reliability] Apply masks non-404 errors
Apply masks non-404 errors DuploHosts.apply catches any DuploError from find and falls back to create, so auth/permission/server errors during list/find can incorrectly be treated as “not found,” triggering unwanted create attempts and hiding the real failure.

Issue description

hosts.apply() currently treats any DuploError thrown by hosts.find() as “host does not exist” and calls create(). This can incorrectly create resources (or attempt to) when the underlying failure is an auth/permission/server error.

Issue Context

  • DuploAPI raises DuploError for 401/403/400/5xx.
  • hosts.find() calls list() which can surface those errors.

Fix Focus Areas

  • src/duplo_resource/hosts.py[71-77]
  • src/duplocloud/client.py[148-164]

Pattern 2: Validate optional CLI inputs (e.g., body, name, required keys) before use and raise a clear domain error instead of allowing AttributeError/KeyError/TypeError from assuming shapes like dict or required fields being present.

Example code before:

def name_from_body(body):
  return body["SecretName"]  # KeyError if missing

def create(body=None):
  body.setdefault("metadata", {})  # AttributeError if body is None

Example code after:

def name_from_body(body):
  if not isinstance(body, dict) or "SecretName" not in body:
    raise DuploError("SecretName is required in request body", 400)
  return body["SecretName"]

def create(body=None):
  if body is None or not isinstance(body, dict):
    raise DuploError("Body is required; pass -f/--cli-input with a JSON/YAML object", 400)
  body.setdefault("metadata", {})
Relevant past accepted suggestions:
Suggestion 1: [correctness] Missing body crashes commands
Missing body crashes commands `argo_wf` and `argo_wf_template` commands call `_ensure_namespace(body, ...)` assuming `body` is a dict; if the user omits `-f/--cli-input`, `args.BODY` remains `None` and the code raises `AttributeError` instead of a `DuploError`. This affects `create/apply` (workflows) and `create/update/apply` (templates).

Issue description

argo_wf / argo_wf_template commands crash with AttributeError when invoked without -f/--cli-input (or when the YAML root is not a mapping), because _ensure_namespace() assumes body is a dict and calls setdefault().

Issue Context

  • args.BODY is an optional flag and can be None.
  • YamlAction can also parse YAML into non-dict types.

Fix Focus Areas

  • src/duplo_resource/argo_wf.py[19-33]
  • src/duplo_resource/argo_wf.py[102-189]
  • src/duplo_resource/argo_wf.py[341-463]

Implementation notes

  • Add a guard in _ensure_namespace():
  • if not isinstance(body, dict): raise DuploError("Body is required; pass -f/--cli-input with a YAML/JSON object", 400)
  • optionally validate body.get(key) is dict when present.
  • (Optional) Add small unit tests for missing body behavior to ensure it raises DuploError instead of AttributeError.

Suggestion 2: [reliability] `name_from_body()` missing validation
`name_from_body()` missing validation `name_from_body()` directly accesses `body["SecretName"]` without validating `body` shape, which can raise `KeyError` and break `apply()` on malformed/partial inputs. This violates the requirement to explicitly handle null/empty/missing fields in edge cases.

Issue description

name_from_body() can raise KeyError when SecretName is missing, which is an unhandled edge case for apply().

Issue Context

apply() depends on name_from_body(body); missing/invalid inputs should fail gracefully with a clear DuploError message.

Fix Focus Areas

  • src/duplo_resource/secret.py[21-22]
  • src/duplo_resource/secret.py[223-253]

Suggestion 3: [correctness] Tenant start/stop crash
Tenant start/stop crash DuploHosts.name_from_body now returns None when FriendlyName is missing, but tenant.start/tenant.stop call service.name_from_body(item) and then invoke hosts.start/hosts.stop with that value (and an extra positional arg), which can raise TypeError/AttributeError at runtime instead of skipping unnamed hosts.

Issue description

DuploHosts.name_from_body() can now return None when a host list item has no FriendlyName. tenant start/tenant stop currently assumes service.name_from_body(item) is always a string and unconditionally calls service.start(...) / service.stop(...), which for hosts can crash (passing None as the name and also passing an extra positional arg).

Issue Context

  • Host list API may return items without FriendlyName.
  • Tenant workflows iterate all hosts and invoke lifecycle operations.

Fix Focus Areas

  • src/duplo_resource/tenant.py[498-504]
  • src/duplo_resource/tenant.py[553-559]
  • src/duplo_resource/hosts.py[309-316]

Pattern 3: Before calling response.json(), explicitly handle empty responses (e.g., HTTP 204 or empty content) by returning {}/None as appropriate to avoid JSONDecodeError during normal delete/request flows.

Example code before:

resp = client.delete(url)
return resp.json()

Example code after:

resp = client.delete(url)
if resp.status_code == 204 or not resp.content:
  return {}
return resp.json()
Relevant past accepted suggestions:
Suggestion 1: [reliability] `delete()` parses empty JSON
`delete()` parses empty JSON `delete()` unconditionally calls `response.json()`, which can fail for 204/empty delete responses. This can cause runtime errors during normal delete operations.

Issue description

delete() paths for Argo workflow resources call .json() unconditionally. For typical delete semantics, servers may return 204 No Content or a 2xx with an empty body, which makes response.json() raise.

Issue Context

Compliance requires checking for 204/empty responses before JSON parsing.

Fix Focus Areas

  • src/duplo_resource/argo_wf.py[146-149]
  • src/duplo_resource/argo_wf.py[428-431]
  • src/duplo_resource/argo_client.py[144-164]

Suggestion 2:

Handle empty API responses gracefully

In the argo_request method, check for empty response bodies or a 204 status code before attempting to parse JSON to prevent a JSONDecodeError.

src/duplo_resource/argo_wf.py [174-177]

 if not (200 <= response.status_code < 300):
   raise DuploError(f"Argo API error: {response.text}", response.status_code)
 
+if response.status_code == 204 or not response.content:
+  return {}
 return response.json()

Pattern 4: Consolidate duplicated logic (prefix handling, normalization, repeated blocks, duplicate helper functions) into a single helper and reuse it everywhere to keep behavior consistent and reduce drift.

Example code before:

def find(name):
  if not name.startswith("pre-"):
    name = "pre-" + name
  ...

def delete(name):
  if not name.startswith("pre-"):
    name = "pre-" + name
  ...

Example code after:

def _ensure_prefix(name, prefix="pre-"):
  return name if name.startswith(prefix) else prefix + name

def find(name):
  name = _ensure_prefix(name)
  ...

def delete(name):
  name = _ensure_prefix(name)
  ...
Relevant past accepted suggestions:
Suggestion 1:

Extract image normalization helper method

Extract the image normalization logic into a shared helper method to avoid duplication and ensure consistent image processing across the codebase.

src/duplo_resource/service.py [710-716]

+def _normalize_image(self, image):
+  """Normalize image name by removing Docker registry prefixes."""
+  return image.removeprefix("docker.io/library/").removeprefix("docker.io/").removeprefix("library/")
+
 def get_pod_image(pod):
   """Get the image of a pod."""
-  raw_image=pod["Containers"][0]["Image"]
+  raw_image = pod["Containers"][0]["Image"]
   self.duplo.logger.debug(f"Retrieved raw image {raw_image} for {pod['InstanceId']}")
-  normalized_image = raw_image.removeprefix("docker.io/library/").removeprefix("docker.io/").removeprefix("library/")
+  normalized_image = self._normalize_image(raw_image)
   self.duplo.logger.debug(f"Returning normalized image {normalized_image} for {pod['InstanceId']}\n")
   return normalized_image

Suggestion 2:

Refactor duplicated prefix logic

The prefix logic is duplicated in both find() and delete() methods. This could lead to inconsistencies if the prefix format changes in one place but not the other. Extract this logic into a helper method to ensure consistent behavior.

src/duplo_resource/aws_secret.py [51-54]

-# if the name has the prefix we good, otherwise add it
-prefix = f"duploservices-{self.duplo.tenant}-"
-if not name.startswith(prefix):
-  name = prefix + name
+# Use a helper method to ensure consistent prefix handling
+name = self._ensure_name_has_prefix(name)

Suggestion 3:

Remove duplicate function definition

This function redefines the get_test_data function that's already imported at the top of the file. This duplication can lead to confusion about which function is being used. Remove this duplicate definition and use the imported function instead.

src/tests/test_cloudfront.py [23-27]

-def get_test_data(name) -> dict:
-  dir = pathlib.Path(__file__).parent.resolve()
-  f = f"{dir}/data/{name}.yaml"
-  with open(f, 'r') as stream:
-    return yaml.safe_load(stream)
+# Remove this duplicate function definition entirely, as it's already imported
+# from .conftest import get_test_data

Pattern 5: Keep user-facing documentation and messages accurate and professional by fixing typos/grammar, ensuring examples reference the correct command/resource, and keeping docstrings aligned with actual function signatures/behavior.

Example code before:

"""Update a secret.
Usage:
  duploctl ssm_param update <name> -pval <newvalue>
Returns:
  AI Agent reponse &  a Chat url.
"""

Example code after:

"""Update a secret.
Usage:
  duploctl aws_secret update <name> -pval <newvalue>
Returns:
  AI Agent response and a chat URL.
"""
Relevant past accepted suggestions:
Suggestion 1: [correctness] CHANGELOG text has typos
CHANGELOG text has typos New release notes contain multiple typos/grammar issues in user-facing documentation. This can confuse users and reduces documentation quality.

Issue description

The newly added CHANGELOG entry contains multiple typos/grammar issues in user-facing release notes (e.g., introducses, entrypoiint, registrered).

Issue Context

Compliance requires documentation and user-facing strings to be correct and consistent to avoid confusing users.

Fix Focus Areas

  • CHANGELOG.md[20-24]

Suggestion 2:

Correct duplicated and unclear docstring

Remove duplicated sentences and ensure consistent, concise phrasing. Adjust articles and capitalization in prose for clarity and correctness.

src/duplo_resource/service.py [626-653]

 """Start a service.
-
-Start a service.
 
 Usage: Basic CLI Use
   ```sh
   duploctl service start <service-name>
   duploctl service start --all
   duploctl service start --targets service1 service2 service3

-Example: Wait for Start

  • This command supports the global --wait. Waits for the desired count of pods to become ready. +Example: Wait for start
  • This command supports the global --wait flag. It waits for the desired number of pods to become ready.
    duploctl service start myapp --wait --loglevel INFO

Args: name: The name of the service to start.

  • all: Boolean flag to start all services. Defaults to False.
  • all: Whether to start all services. Defaults to False. targets: List of service names to start. Cannot be used with name or all.

-Returns: +Returns: A summary containing services that were started successfully and those that encountered errors.

Raises: DuploError: If the service could not be started. """




___

<b>
Suggestion 3</b>:

Fix incorrect resource name in documentation example

**The update method's docstring incorrectly refers to 'ssm_param' instead of 'aws_secret' in the CLI usage example.**

[src/duplo_resource/aws_secret.py [103]](https://github.com/duplocloud/duploctl/pull/118/files#diff-34abfd7c842718be581a6b194282f9a51a493d1c984ce3310b77316480f500feR103-R103)

```diff
 """Update a secret.
 Usage: cli usage
   ```sh
-  duploctl ssm_param update <name> -pval <newvalue>
+  duploctl aws_secret update <name> -pval <newvalue>

- [ ] **Apply this suggestion**



</details>



___

`[Auto-generated best practices - 2026-06-18]`

Clone this wiki locally