diff --git a/mc_openapi/__init__.py b/mc_openapi/__init__.py index 04188a16d9f5cb090be122ae5b3ac5f1253f9516..82190396f2ff50b1bddd2bf9495146ebc4265d13 100644 --- a/mc_openapi/__init__.py +++ b/mc_openapi/__init__.py @@ -1 +1 @@ -__version__ = '2.2.0' +__version__ = '2.3.0' diff --git a/mc_openapi/__main__.py b/mc_openapi/__main__.py index 439bf1998070276470a45651a3486f3d113bdfd4..f6aae0ce68e12eaaf3ade3c372f62a1fa000e238 100644 --- a/mc_openapi/__main__.py +++ b/mc_openapi/__main__.py @@ -1,5 +1,7 @@ #!/usr/bin/env python3 import argparse +import logging +from logging.config import dictConfig import sys from doml_synthesis.data import init_data @@ -19,6 +21,7 @@ from mc_openapi.doml_mc.intermediate_model.metamodel import MetaModelDocs from mc_openapi.doml_mc.mc import ModelChecker from mc_openapi.doml_mc.mc_result import MCResult from mc_openapi.doml_mc.xmi_parser.doml_model import get_pyecore_model +from . import __version__ parser = argparse.ArgumentParser() @@ -47,9 +50,29 @@ def printv(*_args): printv("== Verbose: ON ==") if not args.doml and not args.synth: + dictConfig({ + 'version': 1, + 'formatters': {'default': { + 'format': '[%(asctime)s] %(levelname)s: %(message)s', + }}, + 'handlers': {'wsgi': { + 'class': 'logging.StreamHandler', + 'stream': 'ext://flask.logging.wsgi_errors_stream', + 'formatter': 'default' + }}, + 'root': { + 'level': 'DEBUG', + 'handlers': ['wsgi'] + } + }) + logging.info(f"DOML Model Checker v{__version__}") + # Start the webserver app.run(port=args.port) else: + logging.basicConfig(level=logging.DEBUG, format='* %(message)s') + logging.info(f"DOML Model Checker v{__version__}") + # Run only it via command line doml_path = args.doml reqs_path = args.requirements @@ -111,7 +134,9 @@ else: print("Failed to parse the DOMLR.", file=sys.stderr) exit(-1) - if doml_ver == DOMLVersion.V2_2 or doml_ver == DOMLVersion.V2_2_1: + if (doml_ver == DOMLVersion.V2_2 + or doml_ver == DOMLVersion.V2_2_1 + or doml_ver == DOMLVersion.V2_3): model = get_pyecore_model(doml_xmi, doml_ver) func_reqs = model.functionalRequirements.items for req in func_reqs: diff --git a/mc_openapi/app_config.py b/mc_openapi/app_config.py index e60da49a058797b966cf9de426f0000d98daa4e3..f6234dd5585799ceb66b993316301db9b8ebc97f 100644 --- a/mc_openapi/app_config.py +++ b/mc_openapi/app_config.py @@ -1,5 +1,7 @@ import connexion +from logging.config import dictConfig + app = connexion.App(__name__, specification_dir='openapi/') app.add_api('model_checker.yaml') diff --git a/mc_openapi/doml_mc/domlr_parser/parser.py b/mc_openapi/doml_mc/domlr_parser/parser.py index 811b0d2f4ec5e9d1a9a098822518eb5cc35c47f4..24036f181455600a28484e22fdba8611d0f1e0f8 100644 --- a/mc_openapi/doml_mc/domlr_parser/parser.py +++ b/mc_openapi/doml_mc/domlr_parser/parser.py @@ -1,3 +1,4 @@ +import logging import os import re from typing import Callable, Literal @@ -258,7 +259,7 @@ class DOMLRTransformer(Transformer): ), self.compare_int(sorts, op, rhs1_value, rhs2_value) ) - print( + logging.warning( "Warning: Comparing attributes of two elements with {op} is experimental!\n", "Assumption: the attribute is an Integer." ) diff --git a/mc_openapi/doml_mc/intermediate_model/metamodel.py b/mc_openapi/doml_mc/intermediate_model/metamodel.py index a91d6b0ee4e35f4a40bc85d5db2ade84005eb7e8..bb4fb87194c7298d9a7abe503225e45137902d5a 100644 --- a/mc_openapi/doml_mc/intermediate_model/metamodel.py +++ b/mc_openapi/doml_mc/intermediate_model/metamodel.py @@ -223,7 +223,6 @@ def _find_attribute_class( if aname in c.attributes: return c elif c.superclass is None: - print(c) raise AttributeNotFound( f"Attribute {aname} not found in subclasses of {cname}." ) diff --git a/mc_openapi/doml_mc/mc.py b/mc_openapi/doml_mc/mc.py index 3ef13ff6f339a22b9157b36ee91b0cc18bb364be..b675b6e4e4d65ba6356bfb9e3c2a92cc3b1d1d3c 100644 --- a/mc_openapi/doml_mc/mc.py +++ b/mc_openapi/doml_mc/mc.py @@ -1,3 +1,4 @@ +import logging from multiprocessing import TimeoutError from typing import Optional @@ -22,7 +23,7 @@ class ModelChecker: xmi_model, doml_version) self.metamodel = MetaModels[self.doml_version] self.inv_assoc = InverseAssociations[self.doml_version] - + def check_requirements( self, threads: int = 1, diff --git a/mc_openapi/doml_mc/mc_result.py b/mc_openapi/doml_mc/mc_result.py index aa3bc8ef684b789baf9aed17aaca8fb483ac8765..eb9e5bb7ac04d0fc58bd32365e66c42d6ac36eee 100644 --- a/mc_openapi/doml_mc/mc_result.py +++ b/mc_openapi/doml_mc/mc_result.py @@ -1,4 +1,5 @@ from enum import Enum +import logging from typing import Literal from z3 import CheckSatResult, sat, unsat, unknown @@ -31,6 +32,7 @@ class MCResult(Enum): class MCResults: DONTKNOW_MSG = "Timed out: unable to check some requirements." + SATISFIED_MSG = "All requirements are satisfied." def __init__(self, results: list[tuple[MCResult, Literal["BUILTIN", "USER"], str]]): self.results = results @@ -62,11 +64,14 @@ class MCResults: if some_dontknow: err_msg += '\n' + MCResults.DONTKNOW_MSG + logging.info(err_msg) return MCResult.unsat, err_msg elif some_dontknow: + logging.info(MCResults.DONTKNOW_MSG) return MCResult.dontknow, MCResults.DONTKNOW_MSG else: - return MCResult.sat, "All requirements are satisfied." + logging.info(MCResults.SATISFIED_MSG) + return MCResult.sat, MCResults.SATISFIED_MSG def add_results(self, results: "MCResults"): self.results.extend(results.results) diff --git a/mc_openapi/doml_mc/xmi_parser/doml_model.py b/mc_openapi/doml_mc/xmi_parser/doml_model.py index 775eb727aee3666f263455458e27a5ee0cb1f794..cc77024f974931bd687c3f01a4fe65f76a9ad832 100644 --- a/mc_openapi/doml_mc/xmi_parser/doml_model.py +++ b/mc_openapi/doml_mc/xmi_parser/doml_model.py @@ -1,5 +1,6 @@ import copy import importlib.resources as ilres +import logging import sys from typing import Optional, Tuple @@ -59,14 +60,14 @@ def infer_domlx_version(raw_model: bytes) -> DOMLVersion: if v_str == "v2": return DOMLVersion.V2_0 else: - raise RuntimeError(f"Supplied with DOMLX model of unsupported version {v_str}") + raise RuntimeError(f"DOML model is using an unsupported version: {v_str}") else: return DOMLVersion.V2_0 # Should be DOMLVersion.V1_0, but we use V2_0 because the 2.1 IDE doesn't fill it else: - raise RuntimeError(f"Supplied with malformed DOMLX model or unsupported DOML version.\nIn use version is: {DOMLVersion.V2_0}") + raise RuntimeError(f"The DOML version is unsupported or the model is malformed.\nLowest supported version is: {DOMLVersion.V2_0}") -def parse_doml_model(raw_model: bytes, doml_version: Optional[DOMLVersion]) -> Tuple[IntermediateModel, DOMLVersion]: +def parse_doml_model(raw_model: bytes, doml_version: Optional[DOMLVersion]) -> Tuple[IntermediateModel, DOMLVersion]: # if doml_version is None: # doml_version = infer_domlx_version(raw_model) @@ -83,7 +84,7 @@ def parse_doml_model(raw_model: bytes, doml_version: Optional[DOMLVersion]) -> T doml_version = dv return parse_xmi_model(raw_model, dv), dv except Exception as e: - print(f"Couldn't parse with DOML {dv.value}. Trying another version...") + logging.info(f"Couldn't parse with DOML {dv.value}. Trying another version...") if len(doml_versions) == 0: raise e else: @@ -96,7 +97,7 @@ def parse_doml_model(raw_model: bytes, doml_version: Optional[DOMLVersion]) -> T except: raise Exception("Parsing of DOML failed. Perhaps you are using the wrong DOML version or IDE?") - print(f"Using DOML {doml_version.value}") + logging.info(f"Model '{model.name}' parsed as DOML {doml_version.value}") elp = ELayerParser(MetaModels[doml_version], SpecialParsers[doml_version]) if model.application: @@ -104,13 +105,13 @@ def parse_doml_model(raw_model: bytes, doml_version: Optional[DOMLVersion]) -> T if model.infrastructure: elp.parse_elayer(model.infrastructure) else: - raise RuntimeError("Abstract infrastructure layer is missing.") + raise RuntimeError("Abstract infrastructure layer is missing from DOML.") if model.activeConfiguration: elp.parse_elayer(model.activeConfiguration) if model.activeInfrastructure: im = elp.parse_elayer(model.activeInfrastructure) else: - raise RuntimeError("No active concrete infrastructure layer has been specified.") + raise RuntimeError("No active concrete infrastructure layer has been specified in DOML.") reciprocate_inverse_associations(im, InverseAssociations[doml_version]) @@ -121,17 +122,3 @@ def get_pyecore_model(raw_model: bytes, doml_version: Optional[DOMLVersion]) -> doml_version = infer_domlx_version(raw_model) # TODO: See if its better replaced by the get_model() in parse_doml_version() return parse_xmi_model(raw_model, doml_version) - -from typing import Optional - -def serialize_pyecore_model(root: EObject, doml_version: DOMLVersion = DOMLVersion.V2_0, path: Optional[str] = None): - from pyecore.resources import URI - - # Get rset with metamodel - rset = get_rset(doml_version) - # Create the resource where we'll save the updated model/DOMLX - res = rset.create_resource(URI("./output.domlx")) - # Append updated EObject - res.append(root) - # Serialize to file - res.save() diff --git a/mc_openapi/doml_mc/xmi_parser/ecore.py b/mc_openapi/doml_mc/xmi_parser/ecore.py index 224128cc01a7174c8321418bea4f708368e5eb70..0cc79be1f358d0025e98dd2bdb0257713294952b 100644 --- a/mc_openapi/doml_mc/xmi_parser/ecore.py +++ b/mc_openapi/doml_mc/xmi_parser/ecore.py @@ -1,3 +1,4 @@ +import logging import sys from typing import Callable, Optional @@ -74,7 +75,7 @@ class ELayerParser: elif isinstance(val, EOrderedSet): raw_attrs[eAttr.name] = [str(v) if isinstance(v, EEnumLiteral) else v for v in val] else: - print("Attribute", eAttr.name, "has value", val, "of unexpected type.", file=sys.stderr) + logging.error("Attribute", eAttr.name, "has value", val, "of unexpected type.", file=sys.stderr) attrs = parse_attributes(raw_attrs, mm_class, self.mm) # Get all references and process them diff --git a/mc_openapi/handlers.py b/mc_openapi/handlers.py index d8d04772a59b0d038f08035d2bc4e437a7631218..0190955bd204a9f4ea29423502199fede859f746 100644 --- a/mc_openapi/handlers.py +++ b/mc_openapi/handlers.py @@ -1,4 +1,5 @@ import datetime +import logging import os from mc_openapi.doml_mc.domlr_parser.parser import DOMLRTransformer, Parser from mc_openapi.doml_mc.imc import RequirementStore @@ -12,7 +13,7 @@ def make_error(user_msg, debug_msg=None): result = {"message": user_msg, "timestamp": datetime.datetime.now()} if debug_msg is not None: result["debug_message"] = debug_msg - print(f"ERROR [{datetime.datetime.now()}]: {debug_msg}") + logging.error(debug_msg) return result @@ -28,7 +29,7 @@ def post(body, version=None): doml_version: str = version if doml_version: doml_version = DOMLVersion.get(doml_version) - print(f"Forcing DOML {doml_version.value}") + logging.info(f"Forcing DOML {doml_version.value}") dmc = ModelChecker(doml_xmi, doml_version) @@ -37,7 +38,8 @@ def post(body, version=None): # Add support for Requirements in DOML if (dmc.doml_version == DOMLVersion.V2_2 - or dmc.doml_version == DOMLVersion.V2_2_1): + or dmc.doml_version == DOMLVersion.V2_2_1 + or dmc.doml_version == DOMLVersion.V2_3): domlr_parser = Parser(DOMLRTransformer) model = get_pyecore_model(doml_xmi, dmc.doml_version) func_reqs = model.functionalRequirements.items