Skip to content

Performance Improvements - #10

Open
ShashankFC wants to merge 2 commits into
mainfrom
shashank/performance
Open

ShashankFC wants to merge 2 commits into
mainfrom
shashank/performance

Conversation

@ShashankFC

@ShashankFC ShashankFC commented Nov 27, 2025

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • Refactor
    • Optimized S3 data operations including download, upload, and read functionality with improved code structure
    • Enhanced test statistics processing with better consistency through improved data ordering

✏️ Tip: You can customize this high-level summary in your review settings.


EntelligenceAI PR Summary

This PR improves code quality in stats upload tools through stylistic refactoring and deterministic test case ordering.

  • Refactored S3 access patterns in download_s3_artifacts(), upload_to_s3(), and read_from_s3() by extracting intermediate variables
  • Replaced chained method calls with explicit multi-line operations for better code clarity
  • Added sorting of test cases by file and classname in summarize_test_cases() for consistent output
  • All changes are non-functional improvements focused on maintainability and predictability

@coderabbitai

coderabbitai Bot commented Nov 27, 2025

Copy link
Copy Markdown

Walkthrough

Refactors S3 utility functions to make object retrieval and decompression more explicit, and adds sorting to standardize test case iteration order before aggregation. No functional behavior changes.

Changes

Cohort / File(s) Summary
S3 utility refactors
tools/stats/upload_stats_lib.py
Refactored download_s3_artifacts to explicitly retrieve bucket object inside loop; refactored upload_to_s3 to use intermediate variable for S3 object; refactored read_from_s3 to explicitly decompress gzip body before splitting into lines. No behavior changes.
Test case preprocessing
tools/stats/upload_test_stats.py
Added sorting step in summarize_test_cases to order test cases by file then classname before key-based grouping, standardizing iteration order without changing aggregation logic or output.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

  • These are straightforward refactors making I/O operations more explicit without behavior changes
  • The sorting addition is a simple pre-processing step with clear intent
  • Changes follow consistent patterns across the utilities

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'Performance Improvements' is vague and generic, lacking specificity about which components or functions are being optimized. Provide a more descriptive title that identifies the specific performance improvements, such as 'Refactor S3 operations for clarity in upload_stats_lib' or 'Optimize test case aggregation with pre-sorting'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch shashank/performance

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
tools/stats/upload_test_stats.py (1)

169-175: Deterministic sorting before aggregation

Sorting by file and classname before grouping makes the aggregation order stable without changing the aggregated values; this is a reasonable tradeoff in complexity for more predictable outputs. If you ever need ordering to be fully aligned with get_key, you could extend the sort key to include the remaining key fields, but it’s not required for correctness.

tools/stats/upload_stats_lib.py (2)

75-83: Artifact run-attempt filter never actually skips artifacts

The runattempt check logs that an artifact is being skipped but then continues to download it anyway; there is no continue/return after the mismatch is detected. That means artifacts from other run attempts will still be fetched and processed despite the warning.

Consider explicitly skipping mismatched artifacts, for example:

-    for atom in atoms:
-        if atom.startswith("runattempt"):
-            found_run_attempt = int(atom[len("runattempt") :])
-            if workflow_run_attempt != found_run_attempt:
-                print(
-                    f"Skipping {artifact_name} as it is an invalid run attempt. "
-                    f"Expected {workflow_run_attempt}, found {found_run_attempt}."
-                )
+    for atom in atoms:
+        if atom.startswith("runattempt"):
+            found_run_attempt = int(atom[len("runattempt") :])
+            if workflow_run_attempt != found_run_attempt:
+                print(
+                    f"Skipping {artifact_name} as it is an invalid run attempt. "
+                    f"Expected {workflow_run_attempt}, found {found_run_attempt}."
+                )
+                return artifact_name  # or return early with a sentinel and filter at caller

and adjust the caller if needed to avoid adding skipped artifacts to paths.


166-171: Minor simplification in S3 object construction

The new intermediate s3_obj improves readability. You can simplify the call a bit by dropping unnecessary f-strings:

-    s3_obj = get_s3_resource().Object(f"{bucket_name}", f"{key}")
+    s3_obj = get_s3_resource().Object(bucket_name, key)

Behavior is unchanged; this just tightens up the call.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 21c11da and 994d33b.

📒 Files selected for processing (2)
  • tools/stats/upload_stats_lib.py (3 hunks)
  • tools/stats/upload_test_stats.py (1 hunks)

Comment on lines 105 to 117
for obj in objs:
object_name = Path(obj.key).name
# target an artifact for a specific job_id if provided, otherwise skip the download.
if job_id is not None and str(job_id) not in object_name:
continue
found_one = True
p = Path(Path(obj.key).name)
print(f"Downloading {p}")
s3_resource = get_s3_resource()
bucket_obj = s3_resource.Bucket(GHA_ARTIFACTS_BUCKET)
obj_data = bucket_obj.Object(obj.key).get()
with open(p, "wb") as f:
f.write(obj.get()["Body"].read())
f.write(obj_data["Body"].read())
paths.append(p)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Reuse existing S3 bucket object instead of recreating it per object

Functionality is correct, but you’re re-acquiring the S3 resource and bucket inside the loop even though bucket is already created at Line 99. Reusing bucket is a bit clearer and avoids redundant calls:

@@ def download_s3_artifacts(
-        p = Path(Path(obj.key).name)
-        print(f"Downloading {p}")
-        s3_resource = get_s3_resource()
-        bucket_obj = s3_resource.Bucket(GHA_ARTIFACTS_BUCKET)
-        obj_data = bucket_obj.Object(obj.key).get()
-        with open(p, "wb") as f:
-            f.write(obj_data["Body"].read())
+        p = Path(Path(obj.key).name)
+        print(f"Downloading {p}")
+        obj_body = bucket.Object(obj.key).get()["Body"].read()
+        with open(p, "wb") as f:
+            f.write(obj_body)

This keeps the logic explicit while avoiding extra resource/bucket construction.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In tools/stats/upload_stats_lib.py around lines 105 to 117, the loop recreates
the S3 resource and bucket for every obj even though a bucket variable was
created at line 99; replace the per-iteration get_s3_resource() and bucket_obj =
s3_resource.Bucket(...) calls by reusing the existing bucket (e.g., call
bucket.Object(obj.key).get()), remove the redundant resource/bucket creation,
and keep the rest of the download/write logic unchanged so each object is
fetched from the already-initialized bucket.

Comment on lines +189 to 191
decompressed = gzip.decompress(body).decode()
results = decompressed.split("\n")
return [json.loads(result) for result in results if result]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Decompression path matches upload; consider splitlines() for robustness

Adding gzip.decompress(body).decode() aligns read_from_s3 with upload_to_s3’s gzipped JSONL writes and should fix any mismatch there. For slightly better handling of different newline conventions, you could use splitlines() instead of splitting on "\n":

-    decompressed = gzip.decompress(body).decode()
-    results = decompressed.split("\n")
+    decompressed = gzip.decompress(body).decode()
+    results = decompressed.splitlines()

The current implementation is still correct given how these payloads are produced.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
decompressed = gzip.decompress(body).decode()
results = decompressed.split("\n")
return [json.loads(result) for result in results if result]
decompressed = gzip.decompress(body).decode()
results = decompressed.splitlines()
return [json.loads(result) for result in results if result]
🤖 Prompt for AI Agents
In tools/stats/upload_stats_lib.py around lines 189 to 191, the code
decompresses gzipped JSONL and currently splits the text with .split("\n");
change that to use .splitlines() to robustly handle different newline
conventions (CR, LF, CRLF) and avoid empty-line issues, then continue to filter
out any empty strings and json.loads each non-empty line as before.

@entelligence-ai-pr-reviews

Copy link
Copy Markdown

Entelligence AI Vulnerability Scanner

Status: No security vulnerabilities found

Your code passed our comprehensive security analysis.

Analyzed 2 files in total

@entelligence-ai-pr-reviews

Copy link
Copy Markdown

Review Summary

❌ Rejected Comments (1)

This section lists 1 comments that were identified as fundamentally incorrect and filtered out during review validation. It is only visible on our internal repositories.

tools/stats/upload_stats_lib.py (1)

112-116: download_s3_artifacts may download the same S3 object multiple times if get_s3_resource() returns different resource instances, causing redundant downloads and possible file overwrite/corruption.

📊 Impact Scores:

  • Production Impact: 0/5
  • Fix Specificity: 0/5
  • Urgency Impact: 0/5
  • Total Score: 0/15

Reason for rejection: The bug description is technically inaccurate. The code does not download the same S3 object multiple times. Each object in the iteration (line 105: 'for obj in objs:') is unique and downloaded exactly once. The concern about 'different resource instances' causing 'redundant downloads' is based on a misunderstanding of how the code works - there is no mechanism that would cause the same object to be downloaded multiple times.

Analysis: This comment should be removed because it identifies a non-existent problem. The code correctly downloads each S3 object once during iteration, and there's no redundant downloading occurring. The suggestion, while potentially valid from a code style perspective, addresses an imaginary issue.


🏷️ Draft Comments (6)

Skipped posting 6 draft comments that were valid but scored below your review threshold (>=13/15). Feel free to update them here.

tools/stats/upload_stats_lib.py (2)

180-191: The read_from_s3 function decompresses the entire S3 object into memory before splitting, which can cause high memory usage and potential OOM for large files.

📊 Impact Scores:

  • Production Impact: 4/5
  • Fix Specificity: 3/5
  • Urgency Impact: 3/5
  • Total Score: 10/15

🤖 AI Agent Prompt (Copy & Paste Ready):

In tools/stats/upload_stats_lib.py, lines 180-191, the read_from_s3 function reads and decompresses the entire S3 object into memory, which can cause high memory usage for large files. Refactor the function to stream and parse the gzip file line by line using GzipFile, so that memory usage is proportional to a single line, not the whole file.

47-56,87-87,293-294,308-312: requests.get calls in _get_artifact_urls, _download_artifact, and get_job_name lack a timeout, allowing attackers to cause indefinite hangs or resource exhaustion (DoS).

📊 Impact Scores:

  • Production Impact: 4/5
  • Fix Specificity: 5/5
  • Urgency Impact: 3/5
  • Total Score: 12/15

🤖 AI Agent Prompt (Copy & Paste Ready):

In tools/stats/upload_stats_lib.py, on lines 47-56, 87, 293-294, and 308-312, all `requests.get` calls are missing a `timeout` parameter. This allows attackers or network issues to cause indefinite hangs or resource exhaustion (DoS). Add a reasonable timeout (e.g., `timeout=10`) to every `requests.get` call in these locations.

tools/stats/upload_test_stats.py (4)

221-221: test_case["time"] += test_case["time"] will raise a KeyError if the 'time' attribute is missing from a test case, causing a crash for valid XMLs missing this field.

📊 Impact Scores:

  • Production Impact: 4/5
  • Fix Specificity: 5/5
  • Urgency Impact: 3/5
  • Total Score: 12/15

🤖 AI Agent Prompt (Copy & Paste Ready):

In tools/stats/upload_test_stats.py, line 221, the code assumes every test_case has a 'time' key, but if it's missing, a KeyError will occur and crash the script. Change line 221 to use test_case.get("time", 0.0) instead of test_case["time"] to safely handle missing 'time' fields.

91-98: Inefficient use of try-except inside a loop in process_xml_element for type conversion causes significant overhead on large XMLs.

📊 Impact Scores:

  • Production Impact: 3/5
  • Fix Specificity: 2/5
  • Urgency Impact: 2/5
  • Total Score: 7/15

🤖 AI Agent Prompt (Copy & Paste Ready):

Optimize type conversion in tools/stats/upload_test_stats.py lines 91-98. The current code uses nested try-except blocks inside a loop, which is inefficient for large XMLs. Refactor to check for integer strings before attempting float conversion, minimizing exception overhead. Replace the loop with a more efficient approach as shown.

148-164: Appending results from multiprocessing pool with mp.apply_async and then calling .get() in a loop is less efficient than using mp.map for parallel XML parsing.

📊 Impact Scores:

  • Production Impact: 2/5
  • Fix Specificity: 5/5
  • Urgency Impact: 2/5
  • Total Score: 9/15

🤖 AI Agent Prompt (Copy & Paste Ready):

Refactor tools/stats/upload_test_stats.py lines 148-164 to use `mp.starmap` instead of `mp.apply_async` for parallel XML parsing. This reduces overhead and improves performance when processing many XML files. Prepare the argument list and use `starmap` for batch processing.

52-52: Parsing XML with xml.etree.ElementTree on line 52 allows XML External Entity (XXE) attacks if untrusted XML is processed, leading to arbitrary file disclosure or SSRF.

📊 Impact Scores:

  • Production Impact: 4/5
  • Fix Specificity: 4/5
  • Urgency Impact: 4/5
  • Total Score: 12/15

🤖 AI Agent Prompt (Copy & Paste Ready):

In tools/stats/upload_test_stats.py, line 52, the code uses xml.etree.ElementTree to parse XML files, which is vulnerable to XML External Entity (XXE) attacks if untrusted XML is processed. Replace the import and usage of xml.etree.ElementTree with defusedxml.ElementTree to mitigate XXE and related XML attacks. Update the import at the top and ensure all usages of ET refer to defusedxml.

@entelligence-ai-pr-reviews

Copy link
Copy Markdown

🔬 Multi-Approach Review Summary

This PR was reviewed by 2 different approaches for comparison:

  • 🟢 Standard Reviewer: 0 comments
  • 🟠 LangGraph v3: 3 comments

Total: 3 review comments

Each comment is labeled with its source approach. This allows you to compare different AI review strategies.

🔒 Security Scan: Run once and shared across all approaches for efficiency.

Walkthrough

This PR focuses on code quality improvements in the stats upload tooling. The changes include stylistic refactoring in upload_stats_lib.py to improve readability by breaking down chained method calls into explicit intermediate variables across three S3-related functions. Additionally, upload_test_stats.py introduces deterministic ordering by sorting test cases by file and classname before processing. These modifications enhance code maintainability and ensure consistent, predictable output from the test statistics pipeline without altering any functional behavior.

Changes

File(s) Summary
tools/stats/upload_stats_lib.py Refactored S3 object access patterns across three functions (download_s3_artifacts(), upload_to_s3(), read_from_s3()) by extracting intermediate variables for S3 resources, bucket objects, and decompressed data, replacing chained method calls with explicit multi-line operations for improved readability.
tools/stats/upload_test_stats.py Added sorting of test cases by file and classname using a lambda function in summarize_test_cases() to ensure consistent ordering before processing and grouping.

Sequence Diagram

This diagram shows the interactions between components:

sequenceDiagram
    participant Client as Client Code
    participant S3Service as S3 Resource Service
    participant S3Bucket as S3 Bucket
    participant S3Obj as S3 Object
    participant FileSystem as Local File System

    Note over Client,FileSystem: Artifact Download Flow (Change Block 1)
    
    loop For each object in objs
        Client->>Client: Check if job_id matches object_name
        alt job_id matches or is None
            Client->>S3Service: get_s3_resource()
            S3Service-->>Client: s3_resource
            Client->>S3Bucket: s3_resource.Bucket(GHA_ARTIFACTS_BUCKET)
            S3Bucket-->>Client: bucket_obj
            Client->>S3Obj: bucket_obj.Object(obj.key).get()
            S3Obj-->>Client: obj_data
            Client->>FileSystem: open(path, "wb")
            FileSystem-->>Client: file handle
            Client->>Client: f.write(obj_data["Body"].read())
            Client->>FileSystem: close file
            Client->>Client: paths.append(path)
        end
    end

    Note over Client,S3Obj: Artifact Upload Flow (Change Block 2)
    
    Client->>Client: Prepare gzipped body content
    Client->>S3Service: get_s3_resource()
    S3Service-->>Client: s3_resource
    Client->>S3Obj: s3_resource.Object(bucket_name, key)
    S3Obj-->>Client: s3_obj reference
    Client->>S3Obj: s3_obj.put(Body, ContentEncoding, ContentType)
    S3Obj-->>Client: Upload complete

    Note over Client,S3Obj: Artifact Retrieval Flow (Change Block 3)
    
    Client->>S3Obj: get()["Body"].read()
    S3Obj-->>Client: compressed body
    Client->>Client: gzip.decompress(body).decode()
    Client->>Client: decompressed.split("\n")
    Client->>Client: Parse JSON results
Loading

🔒 Security Analysis

  • Vulnerabilities: 0
  • Bugs: 0
  • Code Smells: 2
  • Security Hotspots: 0

▶️AI Code Reviews for VS Code, Cursor, Windsurf
Install the extension

Note for Windsurf Please change the default marketplace provider to the following in the windsurf settings:

Marketplace Extension Gallery Service URL: https://marketplace.visualstudio.com/_apis/public/gallery

Marketplace Gallery Item URL: https://marketplace.visualstudio.com/items

Entelligence.ai can learn from your feedback. Simply add 👍 / 👎 emojis to teach it your preferences. More shortcuts below

Emoji Descriptions:

  • ⚠️ Potential Issue - May require further investigation.
  • 🔒 Security Vulnerability - Fix to ensure system safety.
  • 💻 Code Improvement - Suggestions to enhance code quality.
  • 🔨 Refactor Suggestion - Recommendations for restructuring code.
  • ℹ️ Others - General comments and information.

Interact with the Bot:

  • Send a message or request using the format:
    @entelligenceai + *your message*
Example: @entelligenceai Can you suggest improvements for this code?
  • Help the Bot learn by providing feedback on its responses.
    @entelligenceai + *feedback*
Example: @entelligenceai Do not comment on `save_auth` function !

Also you can trigger various commands with the bot by doing
@entelligenceai command

The current supported commands are

  1. config - shows the current config
  2. retrigger_review - retriggers the review

More commands to be added soon.

Comment on lines 104 to 119
paths = []
for obj in objs:
object_name = Path(obj.key).name
# target an artifact for a specific job_id if provided, otherwise skip the download.
if job_id is not None and str(job_id) not in object_name:
continue
found_one = True
p = Path(Path(obj.key).name)
print(f"Downloading {p}")
s3_resource = get_s3_resource()
bucket_obj = s3_resource.Bucket(GHA_ARTIFACTS_BUCKET)
obj_data = bucket_obj.Object(obj.key).get()
with open(p, "wb") as f:
f.write(obj.get()["Body"].read())
f.write(obj_data["Body"].read())
paths.append(p)

if not found_one:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Correctness: 🟠 [LangGraph v3] The s3_resource is initialized inside the loop for each object, which is unnecessary and inefficient. Move the initialization outside the loop to avoid redundant calls to get_s3_resource().

📝 Committable Code Suggestion

‼️ Ensure you review the code suggestion before committing it to the branch. Make sure it replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
paths = []
for obj in objs:
object_name = Path(obj.key).name
# target an artifact for a specific job_id if provided, otherwise skip the download.
if job_id is not None and str(job_id) not in object_name:
continue
found_one = True
p = Path(Path(obj.key).name)
print(f"Downloading {p}")
s3_resource = get_s3_resource()
bucket_obj = s3_resource.Bucket(GHA_ARTIFACTS_BUCKET)
obj_data = bucket_obj.Object(obj.key).get()
with open(p, "wb") as f:
f.write(obj.get()["Body"].read())
f.write(obj_data["Body"].read())
paths.append(p)
if not found_one:
found_one = False
paths = []
s3_resource = get_s3_resource()
bucket_obj = s3_resource.Bucket(GHA_ARTIFACTS_BUCKET)
for obj in objs:
object_name = Path(obj.key).name
if job_id is not None and str(job_id) not in object_name:
continue
found_one = True
p = Path(Path(obj.key).name)
print(f"Downloading {p}")
obj_data = bucket_obj.Object(obj.key).get()
with open(p, "wb") as f:
f.write(obj_data["Body"].read())
paths.append(p)
if not found_one:

Comment on lines 163 to 170
json.dump(doc, body)
body.write("\n")

get_s3_resource().Object(
f"{bucket_name}",
f"{key}",
).put(
s3_obj = get_s3_resource().Object(f"{bucket_name}", f"{key}")
s3_obj.put(
Body=gzip.compress(body.getvalue().encode()),
ContentEncoding="gzip",
ContentType="application/json",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Correctness: 🟠 [LangGraph v3] The StringIO object body is used inefficiently. It is written to and then immediately read from, which can be optimized by directly writing the compressed data to the S3 object.

📝 Committable Code Suggestion

‼️ Ensure you review the code suggestion before committing it to the branch. Make sure it replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
json.dump(doc, body)
body.write("\n")
get_s3_resource().Object(
f"{bucket_name}",
f"{key}",
).put(
s3_obj = get_s3_resource().Object(f"{bucket_name}", f"{key}")
s3_obj.put(
Body=gzip.compress(body.getvalue().encode()),
ContentEncoding="gzip",
ContentType="application/json",
compressed_body = gzip.compress('\n'.join(json.dumps(doc) for doc in docs).encode())
s3_obj = get_s3_resource().Object(f"{bucket_name}", f"{key}")
s3_obj.put(
Body=compressed_body,
ContentEncoding="gzip",
ContentType="application/json",
)

Comment on lines 170 to 177
manually instead of using the `test-suite` XML tag because xmlrunner does
not produce reliable output for it.
"""

test_cases = sorted(test_cases, key=lambda x: (x.get("file", ""), x.get("classname", "")))

def get_key(test_case: dict[str, Any]) -> Any:
return (

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Correctness: 🟠 [LangGraph v3] The sorting of test_cases by file and classname is redundant since get_key already handles grouping by these attributes. Remove the sorting to avoid unnecessary computation.

📝 Committable Code Suggestion

‼️ Ensure you review the code suggestion before committing it to the branch. Make sure it replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
manually instead of using the `test-suite` XML tag because xmlrunner does
not produce reliable output for it.
"""
test_cases = sorted(test_cases, key=lambda x: (x.get("file", ""), x.get("classname", "")))
def get_key(test_case: dict[str, Any]) -> Any:
return (
"""Group test cases by classname, file, and job_id. We perform the aggregation
manually instead of using the `test-suite` XML tag because xmlrunner does
not produce reliable output for it.
"""
def get_key(test_case: dict[str, Any]) -> Any:
return (

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