test(integration): add end-to-end codelist extension test suite - #248
test(integration): add end-to-end codelist extension test suite#248jserrano-spec wants to merge 2 commits into
Conversation
Adds a comprehensive integration test suite under `python-test/code-list-tests` to validate the new JSON codelist deserialization and native function registry mechanics from PR finos#33 in runtime repository. Details: - `rune/test.rosetta`: Minimal model defining a `TradeTest` type with `[metadata scheme]` annotations to trigger codelist generation. - `json/*`: A mock Python package (`test_codelists_data`) containing `business-center.json` to prove `importlib.resources` successfully reads zipped wheel data. - `run_codelist_tests.sh`: Orchestration script mirroring repository conventions to scrub the environment, auto-build the generator, generate test models, build local wheels, and execute Pytest. - `tests/test_codelists.py`: Pytest suite explicitly validating `ValidateFpMLCodingSchemeDomain` via the `CachedJSONRuneObjectDeserializer` extension. - `.gitignore`: Formatted for readability and updated to exclude `target/` and `build/` artifacts generated by this specific test suite.
|
Good progress on both the runtime and in defining the end to end tests. These help clarify that there is still some ground to cover to provide Python support for code lists. At a minimum the expectation should be that code list validation comes when executing the BaseClass function Specifically, we would like to replace: if hasattr(trade.businessCenter, "value"):
assert trade.businessCenter.value == "GBLO"
else:
assert trade.businessCenter == "GBLO"with assert trade.validate_model() == [] # empty list = no violationsAt the moment, the
Do we also have a simple way to initialize the code list perhaps by a predefined function that loads the domain based on a string? Further, for model consistency there should ideally be validation when a value is assigned to a attribute of type Additionally, I'd like to suggest a couple of changes to the test to better align with how the Generator repo is organized.
The test model would be reduced to: |
…ture Refactors the codelist integration tests based on PR feedback to align with the generator's global directory structure and native CDM definitions. Details: - `run_codelist_tests.sh`: Relocated generated artifacts and wheels to the global `target/python-code-list-test/` directory. Added cleanup logic for ephemeral `setuptools` build artifacts. - `run_codelist_tests.sh`: Updated dependency installation to pull the upstream `finos/rune-python-runtime` main branch, leveraging the newly merged `CachedJSONRuneObjectDeserializer` (v2.1.0+). - `rune/*`: Removed mocked codelist definitions from `test.rosetta`. Copied native `base-staticdata-codelist-type.rosetta` and `base-staticdata-codelist-func.rosetta` to serve as the deterministic source of truth. - `business-center.json`: Enriched the mock payload with the required `identification` block and optional metadata to satisfy strict Pydantic validation against the true CDM schema. - `test_codelists.py`: Updated namespace imports to reflect the true CDM structure. Replaced manual validation checks with `trade.validate_model() == []` (acting as a placeholder until upcoming runtime TypeAlias enhancements are implemented). - `.gitignore`: Reverted custom integration test rules, as artifacts now live in the globally ignored `target/` directory.
734529e to
dd792da
Compare
|
Thanks for the feedback and architectural guidance! I have just pushed a commit implementing the structural feedback and suggestions you outlined for this PR:
Regarding the simple way to initialize the code list: I completely agree that requiring users to manually instantiate the provider and interact with the native registry is not a great developer experience. We can easily implement a helper function that accepts the target class, max cache size, and directory, initializes the deserializer, and registers it to the It would look something like this: from pathlib import Path
from typing import Type
from rune.runtime.native_registry import rune_register_native
from rune.runtime.extensions.cached_json_rune_object_deserializer import CachedJSONRuneObjectDeserializer
def initialize_codelist_provider(codelist_dir: str | Path, target_class: Type, maxsize: int = 15):
"""
Instantiates the JSON provider and binds it to the Rune native registry.
"""
provider = CachedJSONRuneObjectDeserializer(
codelist_dir=codelist_dir,
target_class=target_class,
maxsize=maxsize
)
rune_register_native("cdm.base.staticdata.codelist.functions.LoadCodeList", provider.load)Architectural Question on Extensions: Now that we have opened the gate for future extensions of Rune-DSL models via the runtime, where do you envision this kind of initialization/injection code living? Should we group these initialization helpers inside a centralized dependency injection/bootstrap module, or should they remain isolated helper scripts within the I will follow up in a separate comment shortly to discuss the |
|
@jserrano-spec - thanks for changes. Note that adding TypeAlias condition and input parameter support is likely to require changes to the generator. Can we avoid initialization unless the developer wants to override defaults? In the case where the developer wants to use the defaults, can't we rely upon Also, can we simplify current replacement |
|
Apologies for the delay since my first message! As promised, here is the follow-up regarding the Runtime and Generator Core Changes. Before deciding our next steps, I wanted to outline my understanding of the requirements, share what I’ve found while investigating the codebase and reading the official documentation, and propose a few potential solutions for your architectural assessment. I believe this will ensure we have a shared understanding of the issues and the path forward. 1. Understanding the GoalTo achieve parity with the Java implementation, we need two things:
2. Investigation & FindingsWhile tracing how to implement this, I investigated the codebase and read
For example, in our integration test, the generator throws away the
typeAlias FpMLCodingScheme(domain string):
string
condition IsValidCodingScheme:
if item exists
then ValidateFpMLCodingSchemeDomain(item, domain)
typeAlias BusinessCenter: FpMLCodingScheme(domain: "business-center")
type TradeTest: <"A minimal type to test codelist integration.">
businessCenter BusinessCenter (1..1)
[metadata scheme]
class TradeTest(BaseDataClass):
businessCenter: Annotated[StrWithMeta, StrWithMeta.serializer(), StrWithMeta.validator(('@scheme', ))] = Field(..., description='')
3. Proposed Solutions for AssessmentBased on these findings and the For the TypeAlias Gap:
# The generator outputs a custom Pydantic type with a validator attached
BusinessCenter = Annotated[str, AfterValidator(validate_fpml_coding_scheme)]
class TradeTest(BaseDataClass):
businessCenter: BusinessCenter
class BusinessCenter(StrWithMeta):
_FQRTN = 'cdm.base.staticdata.codelist.BusinessCenter'
@rune_condition
def condition_0_IsValidCodingScheme(self):
return ValidateFpMLCodingSchemeDomain(self, "business-center")For the Assignment Validation:
class BaseDataClass(BaseModel):
model_config = ConfigDict(validate_assignment=True)
def __setattr__(self, name, value):
super().__setattr__(name, value)
# Custom logic to trigger rune_conditions for 'name' hereI would be glad to try to help contribute to these enhancements in the generator and runtime if you think it would be useful! If so, I would be grateful to know which of the proposed solutions for each issue aligns best with the project's long-term architecture. Please let me know your thoughts on these approaches. |
|
@jserrano-spec so that we can keep a clear record of the conversation about technical alternatives I'm going to move all of this to issue #150 Please use it to add the plan for automatically loading the relevant codelist - the issue is that loading an explicit codelist should only be a concern for developers who want to override defaults. Everyone else should get the default automatically |
|
@jserrano-spec |
Please use Issue #150 for on going technical design questions |
Description
Related Links
Overview
This PR introduces a comprehensive, end-to-end integration test suite to validate the new Codelist Extension mechanism. It provides concrete proof that the generated Python code successfully interacts with the
CachedJSONRuneObjectDeserializerintroduced in Runtime PR #33.The test suite simulates a real-world distribution pipeline: generating code from a Rosetta model, packaging external JSON codelist data as a standalone Python wheel, and evaluating condition checkers directly through the native function registry.
Architecture & Changes
Rosetta Model (
rune/test.rosetta): Introduces a minimal model defining aTradeTesttype with[metadata scheme]annotations to trigger the generation of FpML codelist validation logic.Mock Data Wheel (
json/*): Creates a lightweight Python package (test_codelists_data) containingbusiness-center.json. This successfully proves that the runtime'simportlib.resourceslogic correctly resolves and reads JSON payloads distributed as zipped wheel data.Orchestration Script (
run_codelist_tests.sh): A bash orchestration script mirroring the repository's established testing conventions. It safely handles environmental scrubbing, Maven auto-building, code generation, wheel packaging, and dependency installation (fetching the targeted runtime branch).Pytest Suite (
tests/test_codelists.py): Explicitly wires theCachedJSONRuneObjectDeserializerto the generatedcodelist.functions.LoadCodeListnative function. It asserts thatValidateFpMLCodingSchemeDomainaccurately loads the mock JSON and correctly evaluates valid and invalid codes..gitignore: Refactored for better human readability and updated to exclude the ephemeraltarget/andbuild/artifacts generated by this specific test suite.Testing Instructions
To execute the integration tests locally:
cd python-test/code-list-tests ./run_codelist_tests.sh