Coding standards for AWS SDK code are narrower than general style guides. They cover four things that recur in every integration: how clients and variables are named, how errors are caught and classified, how clients are constructed and reused, and whether the code is type-checked.
Getting these four right once, in writing, prevents the slow drift described in Why AWS SDK Code Needs Governance - the same operation implemented three different ways across three different files.
The client (s3_client / s3Client) is built once, at module load, and imported wherever it's needed - never constructed fresh per call.
Error classification lives in one function (is_not_found / isNotFound), so every caller shares the same definition of "not found."
Types come from mypy_boto3_s3 in Python and natively from @aws-sdk/client-s3 in TypeScript, so a wrong field name is a type error, not a runtime surprise.
The naming pattern (<service>_client / <service>Client) makes the module readable without opening its imports.
Pick one pattern and apply it everywhere: snake_case client and variable names in Python (s3_client, dynamodb_client), camelCase in TypeScript (s3Client, dynamoDbClient), each matching the language's own idiom rather than forcing one language's convention onto the other.
The convention itself matters less than its consistency. A reviewer who sees s3 in one file and s3Client2 in another is looking at drift, not a style choice.
boto3 raises botocore.exceptions.ClientError for every API-level error, with the specific error code nested at err.response["Error"]["Code"]. AWS SDK for JavaScript v3 throws typed exception classes per service (NoSuchKey, ThrottlingException, and so on) that you match with instanceof.
The standard is not to re-derive this mechanism at every call site, but to centralize the classification - "is this error retryable, is this error a expected not-found case" - in one shared helper per error category, imported everywhere it's needed. The mechanics of catching and retrying errors correctly are already covered in this cookbook's pagination-and-retries and debugging sections; the standard here is applying those mechanics the same way across the codebase, not reinventing them.
Both SDKs are designed for a client to be built once and reused across many calls. In boto3, boto3.client("s3") does no network work by itself and is safe to hold at module scope. In SDK v3, new S3Client({}) is similarly cheap to construct and expensive to recreate per request, since each new client re-initializes its middleware stack and connection pool.
The standard: build clients at module load (or once per Lambda cold start, outside the handler), never inside a request handler or a loop. This is covered mechanically in sdk-mechanics; the coding standard is simply to enforce it as a rule, not leave it to individual judgment.
Python's boto3 and botocore are not natively typed; typed clients come from installing boto3-stubs/mypy-boto3 per service, already covered in boto3-fundamentals. TypeScript's @aws-sdk/client-* packages ship their own types, so no extra install is needed.
The standard is to require type checking on new code by default in both languages - mypy or pyright in CI for Python, tsc --noEmit for TypeScript - rather than treating it as optional tooling a developer may or may not turn on.
A written standard only holds if something checks it. A ruff or mypy config in Python, and an eslint config in TypeScript, catch naming and typing violations automatically in CI, which scales better than relying on every reviewer to remember every rule.
Constructing a new client per request. Discards the connection pool and adds latency on every call. Fix: build once at module scope or cold start, reuse everywhere.
Catching a bare Exception/generic error instead of the typed one. Hides which failure actually happened and can swallow bugs unrelated to AWS. Fix: catch ClientError (Python) or the specific typed exception (TypeScript), and branch on the error code.
Re-deriving "is this retryable" logic at every call site. Produces subtly different retry behavior across the codebase. Fix: centralize the classification in one shared helper.
Skipping type checking because "boto3 isn't typed anyway." It is, via boto3-stubs; skipping it means missing the same errors TypeScript would catch for free. Fix: install the relevant boto3-stubs service extra and enable mypy/pyright in CI.
Mixing naming conventions within the same codebase.s3 next to s3_client2 next to S3 makes the code harder to scan. Fix: pick one pattern, write it down, and enforce it with a linter naming rule.
Does the naming convention need to match between Python and TypeScript?
No. Match each language's own idiom - snake_case in Python, camelCase in TypeScript. The goal is internal consistency within each codebase, not identical spelling across languages.
Where should error-handling logic for a specific error code live?
In one shared helper per error category, imported by every call site that needs it. This is a coding-standards concern about organization, not a change to the underlying retry/error-handling mechanics covered elsewhere in this cookbook.
Is it ever acceptable to build a client per request?
Rarely. It adds latency and discards connection reuse. The only common exception is short-lived scripts where a single client is constructed once anyway, which already satisfies the standard.
Do I need boto3-stubs if I'm not using mypy?
Less benefit without a type checker, since IDEs use the stubs for autocomplete too, but the enforcement value comes from running mypy/pyright in CI. Install the stubs either way; wire up the checker when you can.
How do I enforce these standards without slowing down every PR?
Put naming and typing checks in a linter config that runs in CI, so violations are caught automatically rather than relying on a human reviewer to remember every rule.
Does this replace the error-handling and retry guidance elsewhere in this cookbook?
No. The pagination-and-retries and debugging sections cover the mechanics - what to catch, when to retry. This page covers applying those mechanics consistently across a codebase.