Skip to content

Fix py3.9 ci failure for directed-inputs-class#252

Closed
jbdevprimary wants to merge 5 commits intofeat/directed-inputs-decorator-apifrom
cursor/fix-py3-9-ci-failure-for-directed-inputs-class-9453
Closed

Fix py3.9 ci failure for directed-inputs-class#252
jbdevprimary wants to merge 5 commits intofeat/directed-inputs-decorator-apifrom
cursor/fix-py3-9-ci-failure-for-directed-inputs-class-9453

Conversation

@jbdevprimary
Copy link
Collaborator

@jbdevprimary jbdevprimary commented Nov 29, 2025

Fix Python 3.9 CI and address review feedback for directed-inputs-class

Description

This PR resolves the Python 3.9 CI failure for directed-inputs-class and addresses critical review feedback.

Motivation:
The directed-inputs-class CI was failing on Python 3.9 due to incompatibilities with newer typing features (e.g., str | None syntax). Additionally, review feedback highlighted the need for stdin input safety and correct YAML decoding.

Changes:

  • Python 3.9 Compatibility: Implemented robust type hint resolution (_normalize_string_type, _resolve_type_hint) to correctly parse string-based and Union type annotations across Python versions, specifically addressing types.UnionType in Python 3.9.
  • Stdin Safety: Added a 1 MiB maximum size limit for stdin input with a ValueError for oversized payloads. _load_stdin now also includes a graceful JSON decoding fallback.
  • YAML Decoding: Corrected the import and usage of decode_yaml within _decode_value to ensure proper YAML processing and prevent shadowing. Parameters were renamed to decode_from_json and decode_from_yaml for clarity.
  • Docstring Example: Updated the directed_inputs_class import in the docstring.

Fixes #247

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)

How has this been tested?

Local tests were run to verify the changes:

  • uv run --with pytest pytest packages/directed-inputs-class/tests -q (for Python 3.13)
  • .venv-py39/bin/pytest packages/directed-inputs-class/tests -q (for Python 3.9)
  • The new test_stdin_size_limit was added to confirm the stdin safety feature.
  • uv run --with ruff ruff check packages/directed-inputs-class/src/directed_inputs_class/decorators.py packages/directed-inputs-class/tests/test_decorators.py

Test Configuration:

  • Firmware version: N/A
  • Hardware: N/A
  • Toolchain: uv, pytest, ruff
  • SDK: N/A

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream modules

Open in Cursor Open in Web


Note

Adds Py3.9-safe type-hint resolution, stdin size limits, and decoding fixes to directed_inputs_class with a new test.

  • Core (packages/directed-inputs-class/src/directed_inputs_class/decorators.py):
    • Add robust type-hint resolution for Python 3.9: _normalize_string_type, _resolve_type_hint, handle Union/types.UnionType, string annotations, and ForwardRef; integrate into _coerce_value.
    • Add stdin safety: introduce STDIN_MAX_BYTES, limit sys.stdin.read, handle OSError, and raise ValueError for oversized payloads; retain JSON fallback.
    • Fix decoding: use decode_yaml correctly; rename internal flags to decode_from_json/decode_from_yaml; adjust base64 unwrap behavior.
    • Update docstring example to import directed_inputs_class.
  • Tests (packages/directed-inputs-class/tests/test_decorators.py):
    • Add test_stdin_size_limit and import decorators_mod; minor test updates to reflect new behavior.

Written by Cursor Bugbot for commit b787ff7. This will update automatically on new commits. Configure here.

cursoragent and others added 3 commits November 29, 2025 21:48
Add @directed_inputs class decorator and @input_config method decorator
as modern alternatives to DirectedInputsClass inheritance.

## New Features
- `@directed_inputs` class decorator for automatic input loading
- `@input_config` method decorator for per-parameter configuration
- Automatic type coercion (bool, int, float, Path, datetime, dict, list)
- Case-insensitive key lookup
- Full backward compatibility with legacy DirectedInputsClass API

## Components
- `decorators.py`: New decorator implementations
- `InputContext`: Runtime input storage and lookup
- `InputConfig`: Per-parameter configuration dataclass

## Tests
- 23 new tests for decorator API
- 39 total tests passing (16 legacy + 23 new)

Part of terraform-modules migration architectural refactor.
Depends on: PR #246 (docs/wiki-orchestration-update)
Co-authored-by: jon <jon@jonbogaty.com>
Co-authored-by: jon <jon@jonbogaty.com>
@cursor
Copy link
Contributor

cursor bot commented Nov 29, 2025

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

@amazon-q-developer
Copy link
Contributor

Code review in progress. Analyzing for code quality issues and best practices. You can monitor the review status in the checks section at the bottom of this pull request. Detailed findings will be posted upon completion.

Using Amazon Q Developer for GitHub

Amazon Q Developer1 is an AI-powered assistant that integrates directly into your GitHub workflow, enhancing your development process with intelligent features for code development, review, and transformation.

Slash Commands

Command Description
/q <message> Chat with the agent to ask questions or request revisions
/q review Requests an Amazon Q powered code review
/q help Displays usage information

Features

Agentic Chat
Enables interactive conversation with Amazon Q to ask questions about the pull request or request specific revisions. Use /q <message> in comment threads or the review body to engage with the agent directly.

Code Review
Analyzes pull requests for code quality, potential issues, and security concerns. Provides feedback and suggested fixes. Automatically triggered on new or reopened PRs (can be disabled for AWS registered installations), or manually with /q review slash command in a comment.

Customization

You can create project-specific rules for Amazon Q Developer to follow:

  1. Create a .amazonq/rules folder in your project root.
  2. Add Markdown files in this folder to define rules (e.g., cdk-rules.md).
  3. Write detailed prompts in these files, such as coding standards or best practices.
  4. Amazon Q Developer will automatically use these rules when generating code or providing assistance.

Example rule:

All Amazon S3 buckets must have encryption enabled, enforce SSL, and block public access.
All Amazon DynamoDB Streams tables must have encryption enabled.
All Amazon SNS topics must have encryption enabled and enforce SSL.
All Amazon SNS queues must enforce SSL.

Feedback

To provide feedback on Amazon Q Developer, create an issue in the Amazon Q Developer public repository.

For more detailed information, visit the Amazon Q for GitHub documentation.

Footnotes

  1. Amazon Q Developer uses generative AI. You may need to verify generated code before using it in your environment. See the AWS Responsible AI Policy.

Copy link
Contributor

@amazon-q-developer amazon-q-developer bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary

This PR addresses Python 3.9 compatibility issues and adds important security improvements for stdin handling. However, there are several critical issues that need to be resolved before merge:

Critical Issues Found:

  1. Security Vulnerability: The stdin size limit implementation has a race condition that allows memory consumption beyond the intended limit
  2. Logic Errors: Duplicate and inconsistent union type handling logic that could cause unpredictable type resolution behavior
  3. Test Coverage: Missing boundary condition testing for the security feature

Positive Changes:

  • ✅ Proper Python 3.9 compatibility with types.UnionType handling
  • ✅ Addition of stdin size limits for security (implementation needs fixing)
  • ✅ Correct import fix in docstring example
  • ✅ Parameter renaming for clarity (decode_from_json/decode_from_yaml)

Required Actions:

  • Fix the stdin security implementation to prevent the race condition
  • Consolidate the duplicate union type handling logic
  • Enhance test coverage for boundary conditions

The core approach is sound, but the implementation details need refinement to ensure security and consistency.


You can now have the agent implement changes and create commits directly on your pull request's source branch. Simply comment with /q followed by your request in natural language to ask the agent to make changes.

Comment on lines +243 to +249
stdin_data = sys.stdin.read(STDIN_MAX_BYTES + 1)
except OSError:
return {}

if len(stdin_data) > STDIN_MAX_BYTES:
msg = f"Stdin input exceeds maximum size limit ({STDIN_MAX_BYTES} bytes)"
raise ValueError(msg)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛑 Security Vulnerability: The stdin size limit check has a race condition that allows bypassing the security control. Reading STDIN_MAX_BYTES + 1 bytes and then checking the length allows an attacker to consume more memory than intended before the check occurs. This could lead to memory exhaustion attacks1.

Suggested change
stdin_data = sys.stdin.read(STDIN_MAX_BYTES + 1)
except OSError:
return {}
if len(stdin_data) > STDIN_MAX_BYTES:
msg = f"Stdin input exceeds maximum size limit ({STDIN_MAX_BYTES} bytes)"
raise ValueError(msg)
stdin_data = sys.stdin.read(STDIN_MAX_BYTES)
except OSError:
return {}
if len(stdin_data) == STDIN_MAX_BYTES:
# Check if there's more data available
try:
extra = sys.stdin.read(1)
if extra:
msg = f"Stdin input exceeds maximum size limit ({STDIN_MAX_BYTES} bytes)"
raise ValueError(msg)
except OSError:
pass

Footnotes

  1. CWE-400: Uncontrolled Resource Consumption - https://cwe.mitre.org/data/definitions/400.html

Comment on lines +186 to +192
if "|" in cleaned:
parts = [part.strip() for part in cleaned.split("|")]
for part in parts:
resolved = _normalize_string_type(part)
if resolved is not None:
return resolved
return None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛑 Logic Error: The union type parsing logic has a critical flaw that could cause incorrect type resolution. When parsing str | None, if the first part fails to resolve, it continues to the next part and may return None as the resolved type, leading to no type coercion being applied when string coercion was intended.

Suggested change
if "|" in cleaned:
parts = [part.strip() for part in cleaned.split("|")]
for part in parts:
resolved = _normalize_string_type(part)
if resolved is not None:
return resolved
return None
if "|" in cleaned:
parts = [part.strip() for part in cleaned.split("|")]
# Filter out None types first, then try to resolve the first non-None type
non_none_parts = [part for part in parts if part.lower() not in {"none", "nonetype", "type(none)"}]
for part in non_none_parts:
resolved = _normalize_string_type(part)
if resolved is not None:
return resolved
return None

Comment on lines +220 to +232
if origin is Union or (_UNION_TYPE is not None and origin is _UNION_TYPE):
for arg in get_args(target_type):
resolved = _resolve_type_hint(arg)
if resolved is not None:
return resolved
return None

if _UNION_TYPE is not None and isinstance(target_type, _UNION_TYPE):
for arg in getattr(target_type, "__args__", ()):
resolved = _resolve_type_hint(arg)
if resolved is not None:
return resolved
return None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛑 Logic Error: Duplicate union type handling logic creates inconsistent behavior. The same union type resolution is implemented twice with slightly different approaches, which could lead to different results for the same input type depending on which path is taken.

Suggested change
if origin is Union or (_UNION_TYPE is not None and origin is _UNION_TYPE):
for arg in get_args(target_type):
resolved = _resolve_type_hint(arg)
if resolved is not None:
return resolved
return None
if _UNION_TYPE is not None and isinstance(target_type, _UNION_TYPE):
for arg in getattr(target_type, "__args__", ()):
resolved = _resolve_type_hint(arg)
if resolved is not None:
return resolved
return None
origin = get_origin(target_type)
if origin is Union or (_UNION_TYPE is not None and (origin is _UNION_TYPE or isinstance(target_type, _UNION_TYPE))):
# Handle both typing.Union and types.UnionType uniformly
if hasattr(target_type, "__args__"):
args = target_type.__args__
else:
args = get_args(target_type)
for arg in args:
resolved = _resolve_type_hint(arg)
if resolved is not None:
return resolved
return None

Comment on lines +281 to +294
if origin is Union or (_UNION_TYPE is not None and origin is _UNION_TYPE):
args = get_args(target_type)
non_none_types = [t for t in args if t is not type(None)]
if non_none_types:
target_type = non_none_types[0]
else:
return value
elif _UNION_TYPE is not None and isinstance(target_type, _UNION_TYPE):
args = getattr(target_type, "__args__", ())
non_none_types = [t for t in args if t is not type(None)]
if non_none_types:
target_type = non_none_types[0]
else:
return value
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛑 Logic Error: Duplicate union type handling in _coerce_value creates the same inconsistency issue as in _resolve_type_hint. The logic should be unified to prevent different behavior for the same union types.

Suggested change
if origin is Union or (_UNION_TYPE is not None and origin is _UNION_TYPE):
args = get_args(target_type)
non_none_types = [t for t in args if t is not type(None)]
if non_none_types:
target_type = non_none_types[0]
else:
return value
elif _UNION_TYPE is not None and isinstance(target_type, _UNION_TYPE):
args = getattr(target_type, "__args__", ())
non_none_types = [t for t in args if t is not type(None)]
if non_none_types:
target_type = non_none_types[0]
else:
return value
# Handle Union types (including X | None via types.UnionType)
origin = get_origin(target_type)
if origin is Union or (_UNION_TYPE is not None and (origin is _UNION_TYPE or isinstance(target_type, _UNION_TYPE))):
# Handle both typing.Union and types.UnionType uniformly
if hasattr(target_type, "__args__"):
args = target_type.__args__
else:
args = get_args(target_type)
non_none_types = [t for t in args if t is not type(None)]
if non_none_types:
target_type = non_none_types[0]
else:
return value

Comment on lines +174 to +178
oversized = "x" * (decorators_mod.STDIN_MAX_BYTES + 1)
with patch(
"directed_inputs_class.decorators.sys.stdin.read",
return_value=oversized,
), pytest.raises(ValueError, match=r"exceeds maximum size"):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test coverage gap: The test only verifies the error message but doesn't test the actual memory protection. Consider adding a test that verifies the exact boundary condition (data exactly at the limit should pass).

Suggested change
oversized = "x" * (decorators_mod.STDIN_MAX_BYTES + 1)
with patch(
"directed_inputs_class.decorators.sys.stdin.read",
return_value=oversized,
), pytest.raises(ValueError, match=r"exceeds maximum size"):
oversized = "x" * (decorators_mod.STDIN_MAX_BYTES + 1)
exactly_at_limit = "x" * decorators_mod.STDIN_MAX_BYTES
# Test that exactly at limit works
with patch(
"directed_inputs_class.decorators.sys.stdin.read",
return_value=exactly_at_limit,
):
MyService() # Should not raise
# Test that over limit fails
with patch(
"directed_inputs_class.decorators.sys.stdin.read",
return_value=oversized,
), pytest.raises(ValueError, match=r"exceeds maximum size"):

@jbdevprimary
Copy link
Collaborator Author

Closing - stale fix attempt. Original issue addressed differently.

@cursor cursor bot force-pushed the feat/directed-inputs-decorator-api branch from 43ca607 to 8b9889b Compare November 29, 2025 23:23
…dling

- Fix stdin size limit race condition: read limit first, then check for extra
- Remove duplicate union type handling in _coerce_value (handled by _resolve_type_hint)
- Add error handling for type coercion failures

Addresses Amazon Q Developer feedback.
@jbdevprimary jbdevprimary deleted the branch feat/directed-inputs-decorator-api November 30, 2025 00:09
@jbdevprimary jbdevprimary deleted the cursor/fix-py3-9-ci-failure-for-directed-inputs-class-9453 branch November 30, 2025 04:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants