Skip to content

Python a2ui_core: Add basic catalog function implementations#1576

Open
nan-yu wants to merge 1 commit into
a2ui-project:mainfrom
nan-yu:re-arch-3
Open

Python a2ui_core: Add basic catalog function implementations#1576
nan-yu wants to merge 1 commit into
a2ui-project:mainfrom
nan-yu:re-arch-3

Conversation

@nan-yu

@nan-yu nan-yu commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

Summary of Changes

This pull request introduces the core infrastructure and initial implementations for the basic catalog functions in the Python SDK. It provides a robust mechanism for parsing dynamic expressions, defining function schemas, and executing these functions with validated arguments. These changes enable dynamic data binding and complex expression evaluation within the application's catalog system.

Highlights

  • Expression Parser Implementation: Added a new ExpressionParser and Scanner to support parsing dynamic expressions and function calls within strings.
  • Function Implementations: Implemented a suite of basic catalog functions, including arithmetic, logical, comparison, and string formatting operations.
  • Function API Infrastructure: Enhanced the catalog function infrastructure to support dynamic composition of API specifications with executable implementations.
  • Test Coverage: Added comprehensive unit tests for the expression parser and all newly implemented catalog functions.
Activity
  • Pull request created by nan-yu.
  • Automated review requested by user.
  • Multiple high and medium priority feedback items provided by the review agent regarding number parsing, recursion depth, date formatting, and arithmetic edge cases.
  • Summary requested by user.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

Gemini encountered an error creating the review. You can try again by commenting /gemini review.

@nan-yu

nan-yu commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces the a2ui_core Python library, which provides core data models, reactive state management, and JSON schema validation logic for the A2UI protocol (v0.9+). It includes auto-generated component and function schemas, an expression parser, basic catalog function implementations, and comprehensive unit tests. The review feedback highlights several critical issues: negative numbers are not correctly parsed as number literals in the expression parser, a recursion depth check is missing in parse_expression, the date formatter lacks support for several documented TR35 tokens, division by zero is not handled correctly for negative or zero numerators, and redundant float conversions exist in the arithmetic implementations.

Comment on lines +132 to +135
def parse_expression(self, expr: str, depth: int = 0) -> Any:
expr = expr.strip()
if not expr:
return ""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The recursion depth limit check is missing in parse_expression. Because parse_expression is called recursively for nested interpolations (e.g., ${${foo}}), an excessively nested expression can cause a RecursionError or stack overflow. Add the depth check at the entry of parse_expression.

Suggested change
def parse_expression(self, expr: str, depth: int = 0) -> Any:
expr = expr.strip()
if not expr:
return ""
def parse_expression(self, expr: str, depth: int = 0) -> Any:
if depth > self.MAX_DEPTH:
raise ValueError("Max recursion depth reached in parse")
expr = expr.strip()
if not expr:
return ""

Comment on lines +203 to +215
dt = datetime.datetime.fromisoformat(str(val).replace("Z", "+00:00"))
if fmt == "ISO":
return dt.isoformat().replace("+00:00", ".000Z")
py_fmt = (
str(fmt)
.replace("yyyy", "%Y")
.replace("MM", "%m")
.replace("dd", "%d")
.replace("HH", "%H")
.replace("mm", "%M")
.replace("ss", "%S")
)
return dt.strftime(py_fmt)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The current _format_date implementation only replaces a small subset of the documented TR35 tokens (yyyy, MM, dd, HH, mm, ss). Standard tokens like yy, MMM, MMMM, EEEE, E, d, H, hh, h, and a are ignored, leading to broken formatting. Additionally, literal % characters in the format string can cause strftime errors. Update the replacement mapping to support all documented TR35 tokens and escape % characters.

        dt = datetime.datetime.fromisoformat(str(val).replace("Z", "+00:00"))
        if fmt == "ISO":
            return dt.isoformat().replace("+00:00", ".000Z")
        py_fmt = (
            str(fmt)
            .replace("%", "%%")
            .replace("yyyy", "%Y")
            .replace("yy", "%y")
            .replace("MMMM", "%B")
            .replace("MMM", "%b")
            .replace("MM", "%m")
            .replace("M", "%m")
            .replace("EEEE", "%A")
            .replace("E", "%a")
            .replace("dd", "%d")
            .replace("d", "%d")
            .replace("HH", "%H")
            .replace("H", "%H")
            .replace("hh", "%I")
            .replace("h", "%I")
            .replace("mm", "%M")
            .replace("ss", "%S")
            .replace("a", "%p")
        )
        return dt.strftime(py_fmt)

Comment on lines +288 to +299
DivideImplementation = create_function_implementation(
DivideApi,
lambda args, context=None, abort_signal=None: (
math.inf
if _to_float(args["b"]) == 0
else (
int(_to_float(args["a"]) / _to_float(args["b"]))
if (_to_float(args["a"]) / _to_float(args["b"])).is_integer()
else (_to_float(args["a"]) / _to_float(args["b"]))
)
),
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The division implementation always returns math.inf when dividing by zero, regardless of the sign of the numerator a. If a is negative, it should return -math.inf, and if a is zero, it should return math.nan. Refactor the implementation to handle these cases correctly and avoid redundant conversions.

def _divide(args, context=None, abort_signal=None):
    a = _to_float(args["a"])
    b = _to_float(args["b"])
    if b == 0:
        if a > 0:
            return math.inf
        elif a < 0:
            return -math.inf
        else:
            return math.nan
    res = a / b
    return int(res) if res.is_integer() else res

DivideImplementation = create_function_implementation(DivideApi, _divide)

Comment on lines +261 to +286
AddImplementation = create_function_implementation(
AddApi,
lambda args, context=None, abort_signal=None: (
int(_to_float(args["a"]) + _to_float(args["b"]))
if (_to_float(args["a"]) + _to_float(args["b"])).is_integer()
else (_to_float(args["a"]) + _to_float(args["b"]))
),
)

SubtractImplementation = create_function_implementation(
SubtractApi,
lambda args, context=None, abort_signal=None: (
int(_to_float(args["a"]) - _to_float(args["b"]))
if (_to_float(args["a"]) - _to_float(args["b"])).is_integer()
else (_to_float(args["a"]) - _to_float(args["b"]))
),
)

MultiplyImplementation = create_function_implementation(
MultiplyApi,
lambda args, context=None, abort_signal=None: (
int(_to_float(args["a"]) * _to_float(args["b"]))
if (_to_float(args["a"]) * _to_float(args["b"])).is_integer()
else (_to_float(args["a"]) * _to_float(args["b"]))
),
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The Add, Subtract, and Multiply implementations perform redundant float conversions and arithmetic operations (evaluating the same expression up to three times). Refactor them to use helper functions that perform the calculation once.

Suggested change
AddImplementation = create_function_implementation(
AddApi,
lambda args, context=None, abort_signal=None: (
int(_to_float(args["a"]) + _to_float(args["b"]))
if (_to_float(args["a"]) + _to_float(args["b"])).is_integer()
else (_to_float(args["a"]) + _to_float(args["b"]))
),
)
SubtractImplementation = create_function_implementation(
SubtractApi,
lambda args, context=None, abort_signal=None: (
int(_to_float(args["a"]) - _to_float(args["b"]))
if (_to_float(args["a"]) - _to_float(args["b"])).is_integer()
else (_to_float(args["a"]) - _to_float(args["b"]))
),
)
MultiplyImplementation = create_function_implementation(
MultiplyApi,
lambda args, context=None, abort_signal=None: (
int(_to_float(args["a"]) * _to_float(args["b"]))
if (_to_float(args["a"]) * _to_float(args["b"])).is_integer()
else (_to_float(args["a"]) * _to_float(args["b"]))
),
)
def _add(args, context=None, abort_signal=None):
res = _to_float(args["a"]) + _to_float(args["b"])
return int(res) if res.is_integer() else res
AddImplementation = create_function_implementation(AddApi, _add)
def _subtract(args, context=None, abort_signal=None):
res = _to_float(args["a"]) - _to_float(args["b"])
return int(res) if res.is_integer() else res
SubtractImplementation = create_function_implementation(SubtractApi, _subtract)
def _multiply(args, context=None, abort_signal=None):
res = _to_float(args["a"]) * _to_float(args["b"])
return int(res) if res.is_integer() else res
MultiplyImplementation = create_function_implementation(MultiplyApi, _multiply)

- Implement ExpressionParser mirroring the TypeScript version to support dynamic formatString placeholders.
- Add basic catalog function implementations (arithmetic, comparison, logical, string, formatting, and validation).
@nan-yu

nan-yu commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator Author

/gemini summary

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

This pull request introduces the core infrastructure and initial implementations for the basic catalog functions in the Python SDK. It provides a robust mechanism for parsing dynamic expressions, defining function schemas, and executing these functions with validated arguments. These changes enable dynamic data binding and complex expression evaluation within the application's catalog system.

Highlights

  • Expression Parser Implementation: Added a new ExpressionParser and Scanner to support parsing dynamic expressions and function calls within strings.
  • Function Implementations: Implemented a suite of basic catalog functions, including arithmetic, logical, comparison, and string formatting operations.
  • Function API Infrastructure: Enhanced the catalog function infrastructure to support dynamic composition of API specifications with executable implementations.
  • Test Coverage: Added comprehensive unit tests for the expression parser and all newly implemented catalog functions.
Activity
  • Pull request created by nan-yu.
  • Automated review requested by user.
  • Multiple high and medium priority feedback items provided by the review agent regarding number parsing, recursion depth, date formatting, and arithmetic edge cases.
  • Summary requested by user.

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.

1 participant