From 9243405175a65b29717389c1f24a949e6639c7d2 Mon Sep 17 00:00:00 2001 From: dschwartznyc Date: Fri, 19 Apr 2024 06:21:09 -0400 Subject: [PATCH] Changes to utilize the new Python code generator (v2) (#2869) * update to use the new Python Generator (v2). Includes expanded Python unit tests * cleanup validation test * Python Generator Cleanup * revert bundle usage in pom * additional pom cleanup * Updated to align to CDM bundle changes * additional alignment to next version of CDM * release.md clean up * one more release.md update * Update pom.xml to use correct bundle version --------- Co-authored-by: minesh-s-patel --- RELEASE.md | 14 ++++ codefresh.yml | 7 +- pom.xml | 2 +- rosetta-source/pom.xml | 2 +- rosetta-source/src/test/python/__init__.py | 0 rosetta-source/src/test/python/run_tests.sh | 24 ------- .../test/python/semantics/test_cardinality.py | 24 ++++--- .../test/python/semantics/test_conditions.py | 36 ++++++---- .../src/test/python/semantics/test_if_cond.py | 39 ++--------- .../python/semantics/test_local_conditions.py | 51 ++++++++++++++ .../python/semantics/test_pydantic_simple.py | 26 +++++++ .../test/python/semantics/test_validation.py | 68 +++++++++++++++++++ .../src/test/python/serialization/__init__.py | 1 + .../serialization/cdm_comparison_test.py | 44 +++++++----- .../test/python/serialization/dict_comp.py | 25 ++++--- .../serialization/test_trade_state_product.py | 28 ++++---- .../python/src/com/rosetta/test/model/Foo.py | 20 ++++++ .../test_class_member_access_operator.py | 26 +++++++ .../src/test/python/test_helpers/__init__.py | 2 + .../src/test/python/test_helpers/config.py | 11 +++ 20 files changed, 323 insertions(+), 127 deletions(-) create mode 100644 rosetta-source/src/test/python/__init__.py delete mode 100755 rosetta-source/src/test/python/run_tests.sh create mode 100644 rosetta-source/src/test/python/semantics/test_local_conditions.py create mode 100644 rosetta-source/src/test/python/semantics/test_pydantic_simple.py create mode 100644 rosetta-source/src/test/python/semantics/test_validation.py create mode 100644 rosetta-source/src/test/python/serialization/__init__.py create mode 100644 rosetta-source/src/test/python/src/com/rosetta/test/model/Foo.py create mode 100644 rosetta-source/src/test/python/test_class_member_access_operator.py create mode 100644 rosetta-source/src/test/python/test_helpers/__init__.py create mode 100644 rosetta-source/src/test/python/test_helpers/config.py diff --git a/RELEASE.md b/RELEASE.md index e9e87c4cac..2334cdbbbf 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -36,3 +36,17 @@ In Rosetta, select the Textual Browser and inspect the changes identified above. The changes can be reviewed in PR: [#2856](https://github.com/finos/common-domain-model/pull/2856) +# _Python Generator v2_ + +_What is being released?_ + +This release uses the new version of the Python generator (v2) which includes the following changes: + +- Migration to Pydantic 2.x +- More comprehensive support for Rosetta's operators +- Resolves the defect exposed by [PR 2766](https://github.com/finos/common-domain-model/pull/2766) +- Includes an update to the Python Rosetta runtime library used to encapsulate the Pydantic support (now version 2.0.0) + +_Review directions_ + +The changes can be reviewed in PR: [#2869](https://github.com/finos/common-domain-model/pull/2869) diff --git a/codefresh.yml b/codefresh.yml index feb17e721f..635782f1bd 100644 --- a/codefresh.yml +++ b/codefresh.yml @@ -117,14 +117,15 @@ steps: working_directory: ./rosetta-source shell: sh commands: - - python3 -m pip install "pydantic==1.*" + - export PYTHONDONTWRITEBYTECODE=1 + - python3 -m pip install pydantic - python3 -m pip install jsonpickle - - python3 -m pip install ./target/classes/cdm/python/runtime/rosetta_runtime-1.0.0-py3-none-any.whl + - python3 -m pip install ./target/classes/cdm/python/runtime/rosetta_runtime-2.0.0-py3-none-any.whl - |- python3 -m pip wheel --no-deps --only-binary :all: --wheel-dir ./target/classes/cdm/python ./target/classes/cdm/python - python3 -m pip install ./target/classes/cdm/python/python_cdm-*-py3-none-any.whl - python3 -m pip install pytest - - rm -rf ./src/test/python/serialization/__pycache__ ./src/test/python/semantics/__pycache__ + - pytest -p no:cacheprovider ./src/test/python/ DeployDaml: stage: 'build' diff --git a/pom.xml b/pom.xml index 57aad1fd1f..260f17134e 100644 --- a/pom.xml +++ b/pom.xml @@ -81,7 +81,7 @@ oss.sonatype.org - 10.15.3 + 10.15.8 ${rosetta.bundle.version} 9.7.0 diff --git a/rosetta-source/pom.xml b/rosetta-source/pom.xml index 4ce0e0a747..3206d507c6 100644 --- a/rosetta-source/pom.xml +++ b/rosetta-source/pom.xml @@ -742,7 +742,7 @@ ${rosetta.code-gen.version} jar ${project.build.directory}/classes/cdm/python - runtime/rosetta_runtime-1.0.0-py3-none-any.whl + runtime/rosetta_runtime-2.0.0-py3-none-any.whl false diff --git a/rosetta-source/src/test/python/__init__.py b/rosetta-source/src/test/python/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/rosetta-source/src/test/python/run_tests.sh b/rosetta-source/src/test/python/run_tests.sh deleted file mode 100755 index 06ce2638c0..0000000000 --- a/rosetta-source/src/test/python/run_tests.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash -type -P python > /dev/null && PYEXE=python || PYEXE=python3 -if ! $PYEXE -c 'import sys; assert sys.version_info >= (3,10)' > /dev/null 2>&1; then - echo "Found $($PYEXE -V)" - echo "Expecting at least python 3.10 - exiting!" - exit 1 -fi - -MYPATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -ROSETTARUNTIMEDIR="../../../target/classes/cdm/python/runtime" -PYTHONCDMDIR="../../generated/python" -cd $MYPATH - -ACDIR=$(python -c "import sys;print('Scripts' if sys.platform.startswith('win') else 'bin')") - -rm -rf testenv -$PYEXE -m venv --clear testenv -source testenv/$ACDIR/activate - -$PYEXE -m pip install pytest -$PYEXE -m pip install $MYPATH/$ROSETTARUNTIMEDIR/rosetta_runtime-1.0.0-py3-none-any.whl -$PYEXE -m pip install $MYPATH/$PYTHONCDMDIR/python_cdm-*-py3-none-any.whl -pytest -rm -rf testenv .pytest_cache serialization/__pycache__ semantics/__pycache__ \ No newline at end of file diff --git a/rosetta-source/src/test/python/semantics/test_cardinality.py b/rosetta-source/src/test/python/semantics/test_cardinality.py index f015443c18..3a9c75e9c1 100644 --- a/rosetta-source/src/test/python/semantics/test_cardinality.py +++ b/rosetta-source/src/test/python/semantics/test_cardinality.py @@ -1,35 +1,37 @@ -import pytest +'''testing cardinality enforcement''' import datetime +import pytest from cdm.base.datetime.DateList import DateList from rosetta.runtime.utils import ConditionViolationError def test_1_many_fail(): + '''DateList cannot be empty''' dl = DateList(date=[]) with pytest.raises(ConditionViolationError): dl.validate_conditions() -def test_1_many_fail_nopar(): +def test_1_many_fail_empty_constructor(): + '''DateList cannot be empty''' dl = DateList() with pytest.raises(ConditionViolationError): dl.validate_conditions() def test_1_many_pass(): + '''Valid DateList''' dl = DateList(date=[datetime.date(2020, 1, 1)]) dl.validate_conditions() if __name__ == "__main__": - print("first one") - test_1_many_pass() - print("second one") - test_1_many_fail() - print("third one") - test_1_many_fail_nopar() - - -# EOF + print("test_1_many_pass") + test_1_many_pass() + print("test_1_many_fail") + test_1_many_fail() + print("test_1_many_fail_empty_constructor") + test_1_many_fail_empty_constructor() +# EOF diff --git a/rosetta-source/src/test/python/semantics/test_conditions.py b/rosetta-source/src/test/python/semantics/test_conditions.py index 11d1af34da..d4fa93cb58 100644 --- a/rosetta-source/src/test/python/semantics/test_conditions.py +++ b/rosetta-source/src/test/python/semantics/test_conditions.py @@ -1,40 +1,50 @@ +'''Full attribute validation - pydantic and constraints''' import pytest from pydantic import ValidationError from rosetta.runtime.utils import ConditionViolationError from cdm.base.math.NonNegativeQuantity import NonNegativeQuantity from cdm.base.math.UnitType import UnitType +from cdm.base.datetime.Frequency import Frequency +from cdm.base.datetime.PeriodExtendedEnum import PeriodExtendedEnum -''' -def test_recursive_conds(): - unit = UnitType(currency='EUR') - mq = NonNegativeQuantity(value=10, unit=unit) - mq.validate_model() -''' - -def test_recursive_conds_base_fail(): +def test_recursive_conditions_base_fail(): + '''condition_0_AmountOnlyExists violation''' unit = UnitType(currency='EUR') mq = NonNegativeQuantity(unit=unit) with pytest.raises(ConditionViolationError): mq.validate_model() -def test_recursive_conds_direct_fail(): + +def test_recursive_conditions_direct_fail(): + '''Negative quantity condition violation''' unit = UnitType(currency='EUR') mq = NonNegativeQuantity(value=-10, unit=unit) with pytest.raises(ConditionViolationError): mq.validate_model() -def test_attrib_validity(): +def test_bad_attrib_validation(): + '''Invalid attribute assigned''' unit = UnitType(currency='EUR') mq = NonNegativeQuantity(value=10, unit=unit) mq.frequency = 'Blah' with pytest.raises(ValidationError): mq.validate_model() + +def test_correct_attrib_validation(): + '''Valid attribute assigned''' + unit = UnitType(currency='EUR') + mq = NonNegativeQuantity(value=10, unit=unit) + mq.frequency = Frequency(periodMultiplier=1, period=PeriodExtendedEnum.M) + mq.validate_model() + + if __name__ == "__main__": - test_recursive_conds_base_fail() - test_recursive_conds_direct_fail() - test_attrib_validity() + test_recursive_conditions_base_fail() + test_recursive_conditions_direct_fail() + test_bad_attrib_validation() + test_correct_attrib_validation() # EOF diff --git a/rosetta-source/src/test/python/semantics/test_if_cond.py b/rosetta-source/src/test/python/semantics/test_if_cond.py index 500ec945ca..5f04115d2c 100644 --- a/rosetta-source/src/test/python/semantics/test_if_cond.py +++ b/rosetta-source/src/test/python/semantics/test_if_cond.py @@ -1,28 +1,10 @@ import pytest from rosetta.runtime.utils import ConditionViolationError from rosetta.runtime.utils import if_cond -##from cdm.base.math.Measure import Measure from cdm.base.math.QuantitySchedule import QuantitySchedule from cdm.base.math.UnitType import UnitType -##from drr.regulation.cftc.rewrite.CFTCPart43TransactionReport import CFTCPart43TransactionReport +from rosetta.runtime.utils import _resolve_rosetta_attr - -''' -def test_if_cond_pass(): - unit = UnitType(currency='EUR') - multiplier = Measure(value=1) - qs = QuantitySchedule(value=1, unit=unit, multiplier=multiplier) - qs.validate_conditions() - - -def test_if_cond_fail(): - unit = UnitType(currency='EUR') - multiplier = Measure(value=-1) - qs = QuantitySchedule(unit=unit, multiplier=multiplier) - with pytest.raises(ConditionViolationError): - qs.validate_conditions() - -''' def test_if_cond_literals(): class T: def __init__(self): @@ -48,18 +30,6 @@ def __init__(self): self) assert res -''' -def test_if_cond_any(): - class T: - def __init__(self): - self.actionType = "TERM" - self.eventType = 'CORP' - self = T() - fnc = CFTCPart43TransactionReport.condition_0_EventTypeCondition - res = fnc(self) - assert res - -''' def test_if_direct(): class T: def __init__(self): @@ -75,7 +45,10 @@ def __init__(self): 'True', T()) if __name__ == "__main__": - test_if_cond_literals() - test_if_direct() + print('test_if_cond_literals',end='') + test_if_cond_literals() + print('...passed\ntest_if_direct', end='') + test_if_direct() + print('...passed') # EOF diff --git a/rosetta-source/src/test/python/semantics/test_local_conditions.py b/rosetta-source/src/test/python/semantics/test_local_conditions.py new file mode 100644 index 0000000000..abbb017083 --- /dev/null +++ b/rosetta-source/src/test/python/semantics/test_local_conditions.py @@ -0,0 +1,51 @@ +'''Tests of the local registration of conditions''' +import inspect +import pytest +from rosetta.runtime.utils import rosetta_local_condition +from rosetta.runtime.utils import execute_local_conditions +from rosetta.runtime.utils import ConditionViolationError + + +def test_pre_post_conditions(): + '''Tests the registration of functions in two different registries''' + _pre_registry = {} + _post_registry = {} + self = inspect.currentframe() + + # A local PRE condition + @rosetta_local_condition(_pre_registry) + def some_local_condition(): + print(f'Pre {self}') + return True + + # A local POST condition + @rosetta_local_condition(_post_registry) + def some_local_post_condition(): + print(f'Post {self}') + return True + + # Check all PRE conditions + execute_local_conditions(_pre_registry, 'Pre-condition') + + print('Some Code....') + + # Check all POST conditions + execute_local_conditions(_post_registry, 'Post-condition') + + +def test_raise_local_cond(): + '''checks if exception is raised and it is of the correct type''' + _registry = {} + @rosetta_local_condition(_registry) + def some_failing_local_post_condition(): + return False + + with pytest.raises(ConditionViolationError): + execute_local_conditions(_registry, 'condition') + + +if __name__ == '__main__': + test_pre_post_conditions() + test_raise_local_cond() + +# EOF diff --git a/rosetta-source/src/test/python/semantics/test_pydantic_simple.py b/rosetta-source/src/test/python/semantics/test_pydantic_simple.py new file mode 100644 index 0000000000..8aeedcb430 --- /dev/null +++ b/rosetta-source/src/test/python/semantics/test_pydantic_simple.py @@ -0,0 +1,26 @@ +# pylint: disable=unused-import,missing-function-docstring,invalid-name +from datetime import date +from cdm.event.common.Trade import Trade +from cdm.event.common.TradeIdentifier import TradeIdentifier +from cdm.product.template.TradableProduct import TradableProduct +from cdm.product.template.Product import Product + + +def test_trade(): + product = Product() + tradableProduct = TradableProduct(product=product) + tradeIdentifier=[TradeIdentifier(issuer='Acme Corp')] + + t = Trade( + tradeDate=date(2023, 1, 1), + tradableProduct=tradableProduct, + tradeIdentifier=tradeIdentifier + ) + print(t.model_dump()) + print('Done!') + + +if __name__ == '__main__': + test_trade() + +# EOF diff --git a/rosetta-source/src/test/python/semantics/test_validation.py b/rosetta-source/src/test/python/semantics/test_validation.py new file mode 100644 index 0000000000..1694f111ec --- /dev/null +++ b/rosetta-source/src/test/python/semantics/test_validation.py @@ -0,0 +1,68 @@ +# pylint: disable=missing-function-docstring,missing-module-docstring,invalid-name +import os +import sys +from datetime import date +import logging +from pathlib import Path +from cdm.event.common.Trade import Trade +from cdm.event.common.TradeIdentifier import TradeIdentifier +from cdm.product.template.TradableProduct import TradableProduct +from cdm.product.template.Product import Product +from cdm.product.template.TradeLot import TradeLot +from cdm.product.common.settlement.PriceQuantity import PriceQuantity +from cdm.base.staticdata.party.Party import Party +from cdm.base.staticdata.party.PartyIdentifier import PartyIdentifier +from cdm.base.staticdata.party.Counterparty import Counterparty +from cdm.base.staticdata.party.CounterpartyRoleEnum import CounterpartyRoleEnum +from cdm.base.staticdata.asset.common.Index import Index +from cdm.base.staticdata.identifier.AssignedIdentifier import AssignedIdentifier +from cdm.event.common.TradeState import TradeState +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir))) +from test_helpers.config import CDM_JSON_SAMPLE_SOURCE + +def test_trade(): + priceQuantity = PriceQuantity() + tradeLot = TradeLot(priceQuantity=[priceQuantity]) + product = Product(index=Index()) + counterparty = [ + Counterparty(role=CounterpartyRoleEnum.PARTY_1, + partyReference=Party( + partyId=[PartyIdentifier(identifier='Acme Corp')])), + Counterparty( + role=CounterpartyRoleEnum.PARTY_2, + partyReference=Party( + partyId=[PartyIdentifier(identifier='Wile E. Coyote')])) + ] + tradableProduct = TradableProduct(product=product, + tradeLot=[tradeLot], + counterparty=counterparty) + assignedIdentifier = AssignedIdentifier(identifier='BIG DEAL!') + tradeIdentifier = [ + TradeIdentifier(issuer='Acme Corp', + assignedIdentifier=[assignedIdentifier]) + ] + + t = Trade(tradeDate=date(2023, 1, 1), + tradableProduct=tradableProduct, + tradeIdentifier=tradeIdentifier) + exceptions = t.validate_model(raise_exc=False) + assert not exceptions + + +def test_rates(): + path = os.path.join(os.path.dirname(__file__), CDM_JSON_SAMPLE_SOURCE, 'rates', 'bond-option-uti.json') + json_str = Path(path).read_text(encoding='utf8') + ts = TradeState.model_validate_json(json_str) + print(repr(ts)) + + exceptions = ts.validate_model(raise_exc=False) + assert not exceptions + + +if __name__ == '__main__': + logging.getLogger().setLevel(logging.DEBUG) + test_trade() + test_fx() + print('Done!') + +# EOF diff --git a/rosetta-source/src/test/python/serialization/__init__.py b/rosetta-source/src/test/python/serialization/__init__.py new file mode 100644 index 0000000000..f76aff80ce --- /dev/null +++ b/rosetta-source/src/test/python/serialization/__init__.py @@ -0,0 +1 @@ +__all__ = ["cdm_comparison_test", "dict_comp"] \ No newline at end of file diff --git a/rosetta-source/src/test/python/serialization/cdm_comparison_test.py b/rosetta-source/src/test/python/serialization/cdm_comparison_test.py index d590fedf10..de8189d1ba 100644 --- a/rosetta-source/src/test/python/serialization/cdm_comparison_test.py +++ b/rosetta-source/src/test/python/serialization/cdm_comparison_test.py @@ -1,29 +1,37 @@ -''' Jsonn test utilities''' +''' Compare CDM / JSON deserialization and serialization''' import json from pathlib import Path import sys import os -dirPath = os.path.dirname(__file__) -sys.path.append(os.path.join(dirPath)) - -from dict_comp import dict_comp +from pydantic import ValidationError from cdm.version import __build_time__ - +from cdm.event.common.TradeState import TradeState +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir))) +from serialization.dict_comp import dict_comp +from test_helpers.config import CDM_JSON_SAMPLE_SOURCE def cdm_comparison_test_from_file(path, class_name): '''loads the json from a file and runs the comparison''' - print('testing: ' + path + ' with className: ' + class_name.__name__ + ' using CDM built at: ' + __build_time__) + print('testing: ', + path, + ' with className: ', + class_name.__name__, + ' using CDM built at: ', + __build_time__) json_str = Path(path).read_text() - cdm_comparison_test_from_string(json_str, class_name) - - -def cdm_comparison_test_from_string(json_str, class_name): - '''loads the json from a string and runs the comparison''' - cdm_object = class_name.parse_raw(json_str) - json_data_out = cdm_object.json(exclude_defaults=True, indent=4) - orig_dict = json.loads(json_str) - generated_dict = json.loads(json_data_out) - assert dict_comp(orig_dict, generated_dict), "Failed corrected dict comparison" - print('passed: dicts matched') + json_dict = json.loads (json_str) + print('json_dict["trade"]["tradeDate"]: ' + json.dumps(json_dict["trade"]["tradeDate"])) + try: + print('raw parse from json_str') + cdm_object = class_name.model_validate_json(json_str) + trade = cdm_object.trade + print('trade.tradeDate:', str(trade.tradeDate)) + json_data_out = cdm_object.model_dump_json(indent=4, exclude_defaults=True) + generated_dict = json.loads(json_data_out) + assert dict_comp(json_dict, generated_dict), "Failed corrected dict comparison" + print('passed: dicts matched') + except ValidationError as e: + print('failed to parse') + print(e) # EOF diff --git a/rosetta-source/src/test/python/serialization/dict_comp.py b/rosetta-source/src/test/python/serialization/dict_comp.py index 284223f0b5..e20d199443 100644 --- a/rosetta-source/src/test/python/serialization/dict_comp.py +++ b/rosetta-source/src/test/python/serialization/dict_comp.py @@ -1,7 +1,13 @@ +'''dict/list recursive comparison''' def dict_comp(d1, d2, prefix=''): + '''compare recursively a dictionary/list''' if d1 == d2: return True - if type(d1) != type(d2): + if type(d1) != type(d2): # pylint: disable=unidiomatic-typecheck + if (isinstance(d1, (float, int)) and isinstance(d2, str) + or isinstance(d1, str) + and isinstance(d2, (float, int))) and float(d1) == float(d2): + return True print(f'Types differ for path {prefix} - d1: {type(d1)}, d2: {type(d2)}') return False if isinstance(d1, dict) and isinstance(d2, dict): @@ -11,17 +17,16 @@ def dict_comp(d1, d2, prefix=''): for k, v in d1.items(): if not dict_comp(v, d2[k], f'{prefix}.{k}'): return False - elif isinstance(d1, list) and isinstance(d2, list): + return True + if isinstance(d1, list) and isinstance(d2, list): + if len(d1) != len(d2): + print(f'Lists at {prefix} are of different length d1: {len(d1)}, d2: {len(d2)}') + return False for i, (e1, e2) in enumerate(zip(d1, d2)): if not dict_comp(e1, e2, f'{prefix}[{i}]'): return False - if len(d1) != len(d2): - print(f'Lists at {prefix} are of diffrent lenght d1: {len(d1)}, d2: {len(d2)}') - else: - print(f'Lists at {prefix} differ for unknown reason?!?') - return False - else: - print(f'Elements for ptah {prefix} differ: d1: {d1}, d2 {d2}') - return False + return True + print(f'Elements for path {prefix} differ: d1: {d1}, d2 {d2}') + return False # EOF diff --git a/rosetta-source/src/test/python/serialization/test_trade_state_product.py b/rosetta-source/src/test/python/serialization/test_trade_state_product.py index 6a309ccabc..deecbdb765 100644 --- a/rosetta-source/src/test/python/serialization/test_trade_state_product.py +++ b/rosetta-source/src/test/python/serialization/test_trade_state_product.py @@ -1,19 +1,21 @@ +'''read and validate trade state specified by cdm_sample''' import sys -import pytest -import argparse - +import os +import inspect from cdm.event.common.TradeState import TradeState -import sys,os +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir))) +from test_helpers.config import CDM_JSON_SAMPLE_SOURCE +from serialization.cdm_comparison_test import cdm_comparison_test_from_file -from cdm_comparison_test import cdm_comparison_test_from_file -def test_trade_state (cdm_sample=None): - if cdm_sample == None: - dirPath = os.path.dirname(__file__) - sys.path.append(os.path.join(dirPath)) - cdm_sample = dirPath + '/../../../main/resources/result-json-files/fpml-5-10/products/rates/EUR-Vanilla-account.json' - cdm_comparison_test_from_file (cdm_sample, TradeState) +def test_trade_state (cdm_sample_in=None): + '''test trade state''' + dir_path = os.path.dirname(__file__) + if cdm_sample_in is None: + sys.path.append(os.path.join(dir_path)) + cdm_sample_in = os.path.join(dir_path, CDM_JSON_SAMPLE_SOURCE, 'rates', 'EUR-Vanilla-account.json') + cdm_comparison_test_from_file (cdm_sample_in, TradeState) if __name__ == "__main__": - cdm_sample = sys.argv[1] if len(sys.argv) > 1 else None - test_trade_state(cdm_sample) \ No newline at end of file + cdm_sample = sys.argv[1] if len(sys.argv) > 1 else None + test_trade_state(cdm_sample) \ No newline at end of file diff --git a/rosetta-source/src/test/python/src/com/rosetta/test/model/Foo.py b/rosetta-source/src/test/python/src/com/rosetta/test/model/Foo.py new file mode 100644 index 0000000000..fd1221076c --- /dev/null +++ b/rosetta-source/src/test/python/src/com/rosetta/test/model/Foo.py @@ -0,0 +1,20 @@ +# pylint: disable=line-too-long, invalid-name, missing-function-docstring, missing-module-docstring, superfluous-parens +# pylint: disable=wrong-import-position, unused-import, unused-wildcard-import, wildcard-import, wrong-import-order, missing-class-docstring +from __future__ import annotations +from typing import List, Optional +from datetime import date +from datetime import time +from datetime import datetime +from decimal import Decimal +from pydantic import Field +from rosetta.runtime.utils import BaseDataClass + +__all__ = ['Foo'] + + +class Foo(BaseDataClass): + one: int = Field(..., description="") + three: List[int] = Field([], description="") + two: Optional[int] = Field(None, description="") + +#EOF diff --git a/rosetta-source/src/test/python/test_class_member_access_operator.py b/rosetta-source/src/test/python/test_class_member_access_operator.py new file mode 100644 index 0000000000..aa9e8d0d7d --- /dev/null +++ b/rosetta-source/src/test/python/test_class_member_access_operator.py @@ -0,0 +1,26 @@ +import pytest +from rosetta.runtime.utils import _resolve_rosetta_attr +from src.com.rosetta.test.model.Foo import * + +foo = Foo(one=42, three=[1, 2, 3]) + +def test_attribute_single (): + assert _resolve_rosetta_attr(foo, 'one') == 42 +def test_attribute_optional (): + assert _resolve_rosetta_attr(foo, 'two') is None +def test_attribute_multi (): + assert _resolve_rosetta_attr(foo, 'three') == [1, 2, 3] +def test_attribute_single_collection (): + assert _resolve_rosetta_attr([foo, foo], 'one') == [42, 42] +def test_attribute_optional_collection (): + assert _resolve_rosetta_attr([foo, foo], 'two') is None +def test_attribute_multi_collection (): + assert _resolve_rosetta_attr([foo, foo], 'three') == [1, 2, 3, 1, 2, 3] + +if __name__ == "__main__": + test_attribute_single () + test_attribute_optional () + test_attribute_multi () + test_attribute_single_collection () + test_attribute_optional_collection () + test_attribute_multi_collection () \ No newline at end of file diff --git a/rosetta-source/src/test/python/test_helpers/__init__.py b/rosetta-source/src/test/python/test_helpers/__init__.py new file mode 100644 index 0000000000..dd1e446f1c --- /dev/null +++ b/rosetta-source/src/test/python/test_helpers/__init__.py @@ -0,0 +1,2 @@ +'''init for config''' +__all__ = ["config"] diff --git a/rosetta-source/src/test/python/test_helpers/config.py b/rosetta-source/src/test/python/test_helpers/config.py new file mode 100644 index 0000000000..00367601b2 --- /dev/null +++ b/rosetta-source/src/test/python/test_helpers/config.py @@ -0,0 +1,11 @@ +'''used to establish configuration shared across the tests''' +import pathlib + +CDM_JSON_SAMPLE_SOURCE = str(pathlib.Path(pathlib.Path().parent.absolute(), + 'src', + 'main', + 'resources', + 'result-json-files', + 'fpml-5-10', + 'products')) +# EOF