diff --git a/docs/source/API.rst b/docs/source/API.rst index 62ab077..493bf41 100644 --- a/docs/source/API.rst +++ b/docs/source/API.rst @@ -6,191 +6,11 @@ common.analyzer Module .. automodule:: common.analyzer :members: - -check_model Function --------------------- - -.. automethod:: common.analyzer.check_model(model_str: str, rate_law_classifications_path: str=None, abort_on_complicated_rate_laws: bool=True, excluded_codes: List[int]=[]) - - Checks the SBML model for rate law errors and warnings. - - **Parameters:** - - - **model_str** (*str*): Path to the model file, or the string representation of model. - - **rate_law_classifications_path** (*str*): Path to the rate law classification file. - - **abort_on_complicated_rate_laws** (*bool*): If True, the check will abort if the rate law is too complicated to process. - - **excluded_codes** (*List[int]*): List of codes of the checks to exclude. If None, all checks are performed. - - **Returns:** - - The results of the checks as a result object, can be printed or converted to string. - -Analyzer --------- - -.. autoclass:: common.analyzer.Analyzer - :members: :noindex: - :undoc-members: - :show-inheritance: - - .. automethod:: list_all_checks() - - Returns a string representation of all the checks. - - **Returns:** - - A string listing all error and warning checks. - - .. automethod:: list_check(code: int) - - Returns a string representation of the check corresponding to the provided code. - - **Parameters:** - - - **code** (*int*): The code of the check. - - **Returns:** - - A string describing the check corresponding to the provided code. - - .. automethod:: __init__(model_str: str, rate_law_classifications_path: str=None, abort_on_complicated_rate_laws: bool=True) - - Initializes the Analyzer class. - - **Parameters:** - - - **model_str** (*str*): Path to the model file, or the string representation of model. - - **rate_law_classifications_path** (*str*): Path to the rate law classification file. - - **abort_on_complicated_rate_laws** (*bool*): If True, the check will abort if the rate law is too complicated to process. - - **Examples:** - - :: - - from ratesb_python import Analyzer - analyzer = Analyzer("path/to/biomodel.xml", "path/to/rate_laws.json") - analyzer.check_all() - results = analyzer.results - print(str(results)) - str(results) - - .. automethod:: check_except(excluded_codes: Optional[List[int]]=[]) - - Performs all checks except the ones corresponding to the provided list of error or warning codes. - - **Parameters:** - - - **excluded_codes** (*Optional[List[int]]*): List of codes of the checks to exclude. If None, all checks are performed. - - **Updates:** - - The results of the check(s) to self.results. - - .. automethod:: check_all() - - Performs all checks. - - **Updates:** - - The results of the check_all to self.results. - - .. automethod:: checks(codes: List[int]) - - Performs multiple checks based on the provided list of error or warning codes. If no codes are provided, all checks are performed. - - **Parameters:** - - - **codes** (*List[int]*): List of codes of the checks to perform. - - **Updates:** - - The results of the checks to self.results. Results ------- .. autoclass:: common.results.Results :members: - :noindex: - :undoc-members: - :show-inheritance: - - .. automethod:: __init__() - - Initializes the Results class. - - .. automethod:: add_message(reaction_name: str, code: int, message: str, is_warning=True) - - Adds a new result to the results list. - - **Parameters:** - - - **reaction_name** (*str*): Name of the reaction. - - **code** (*int*): Code of the message. - - **message** (*str*): The message to be added. - - **is_warning** (*bool*): If the message is a warning message. - - .. automethod:: clear_results() - - Clears all results from the results list. - - .. automethod:: get_all_warnings() - - Returns all the warnings as a list of strings. - - **Returns:** - - A list of warnings. - - .. automethod:: get_all_errors() - - Returns all the errors. - - **Returns:** - - A list of errors. - - .. automethod:: get_messages_by_reaction(reaction_name: str) - - Returns all the messages for a specific reaction. - - **Parameters:** - - - **reaction_name** (*str*): Name of the reaction. - - **Returns:** - - A list of messages for the specified reaction. - - .. automethod:: remove_messages_by_reaction(reaction_name: str) - - Removes all the messages for a specific reaction. - - **Parameters:** - - - **reaction_name** (*str*): Name of the reaction. - - .. automethod:: count_messages() - - Returns the total number of errors and warnings. - - **Returns:** - - The total count of errors and warnings. - - .. automethod:: count_errors() - - Returns the total number of errors. - - **Returns:** - - The total count of errors. - - .. automethod:: count_warnings() - - Returns the total number of warnings. - - **Returns:** - - The total count of warnings. + :noindex: \ No newline at end of file diff --git a/docs/source/conf.py b/docs/source/conf.py index 1fd8933..3d45f86 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -1,5 +1,6 @@ import os import sys +import sphinx_rtd_theme sys.path.insert(0, os.path.abspath('../../ratesb_python')) @@ -37,9 +38,15 @@ # ones. extensions = [ 'sphinx.ext.autodoc', + 'sphinx.ext.mathjax', + 'sphinx.ext.viewcode', 'sphinx.ext.napoleon' ] +html_theme = "sphinx_rtd_theme" + +html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] + napoleon_google_docstring = True napoleon_numpy_docstring = True diff --git a/ratesb_python/common/analyzer.py b/ratesb_python/common/analyzer.py index 4af1ddb..fd40e33 100644 --- a/ratesb_python/common/analyzer.py +++ b/ratesb_python/common/analyzer.py @@ -144,6 +144,10 @@ class Analyzer: some rules. It uses sympy for processing mathematical symbol expressions, libsbmlpython for processing SBML models, and SBMLKinetics for analyzing SBML models and classifying rate laws. The class can return errors and warnings based on the specified checks. + + Attributes: + results (Results): An instance of the Results class used to store and retrieve + analysis results. For more details, refer to the documentation of the Results class. """ @staticmethod diff --git a/ratesb_python/common/results.py b/ratesb_python/common/results.py index ef3f1a9..d313f94 100644 --- a/ratesb_python/common/results.py +++ b/ratesb_python/common/results.py @@ -2,66 +2,77 @@ class Results: """ - A class used to represent the analysis results of a model. - - Rep invariant: - num_errors >= 0, num_warnings >= 0 - num_errors = number of errors, num_warnings = number of warnings - - Attributes: - results (OrderedDict): A dictionary of the results. - num_errors (int): The number of errors. - num_warnings (int): The number of warnings. + A class to represent the analysis results of a model. """ - + def __init__(self): - self.results = OrderedDict() - self.num_errors = 0 - self.num_warnings = 0 + """ + Initializes the Results instance with an empty OrderedDict for results, + and sets the number of errors and warnings to zero. + """ + self._results = OrderedDict() + self._num_errors = 0 + self._num_warnings = 0 - def add_message(self, reaction_name: str, code: int, message: str, is_warning=True): + def add_message(self, reaction_name: str, code: int, message: str, is_warning: bool = True): """ - Adds a new result to the results list. + Adds a new message to the results list. Args: - reaction_name (str): name of the reaction - code (int): Code of the message. - message (str): The message to be added. - is_warning (bool): if the message is warning message. + reaction_name (str): The name of the reaction. + code (int): The code associated with the message. + message (str): The message content. + is_warning (bool): Indicates if the message is a warning. Defaults to True. + + Raises: + None """ if is_warning: - self.num_warnings += 1 + self._num_warnings += 1 else: - self.num_errors += 1 - if reaction_name not in self.results: - self.results[reaction_name] = [] - self.results[reaction_name].append({"code": code, "message": message, "is_warning": is_warning}) + self._num_errors += 1 + + if reaction_name not in self._results: + self._results[reaction_name] = [] + self._results[reaction_name].append({"code": code, "message": message, "is_warning": is_warning}) def clear_results(self): """ - Clears all results from the results list. + Clears all results from the results list, resetting the number of errors and warnings. + + Args: + None + + Raises: + None """ - self.results = OrderedDict() - self.num_errors = 0 - self.num_warnings = 0 + self._results = OrderedDict() + self._num_errors = 0 + self._num_warnings = 0 - def get_warnings(self): + def get_all_warnings(self): """ - Returns all the warnings. + Retrieves all warnings as a dictionary of reaction names to warning messages. + + Returns: + OrderedDict: A dictionary containing warnings grouped by reaction names. """ warnings = OrderedDict() - for reaction_name, messages in self.results.items(): + for reaction_name, messages in self._results.items(): warnings[reaction_name] = [msg for msg in messages if msg['is_warning']] if len(warnings[reaction_name]) == 0: del warnings[reaction_name] return warnings - def get_errors(self): + def get_all_errors(self): """ - Returns all the errors. + Retrieves all errors as a dictionary of reaction names to error messages. + + Returns: + OrderedDict: A dictionary containing errors grouped by reaction names. """ errors = OrderedDict() - for reaction_name, messages in self.results.items(): + for reaction_name, messages in self._results.items(): errors[reaction_name] = [msg for msg in messages if not msg['is_warning']] if len(errors[reaction_name]) == 0: del errors[reaction_name] @@ -69,52 +80,72 @@ def get_errors(self): def get_messages_by_reaction(self, reaction_name: str): """ - Returns all the messages for a specific reaction. + Retrieves all messages for a specific reaction. + + Args: + reaction_name (str): The name of the reaction. + + Returns: + list: A list of messages for the specified reaction. """ - return self.results.get(reaction_name, []).copy() + return self._results.get(reaction_name, []).copy() def remove_messages_by_reaction(self, reaction_name: str): """ - Removes all the messages for a specific reaction. + Removes all messages for a specific reaction. + + Args: + reaction_name (str): The name of the reaction. + + Raises: + None """ - if reaction_name in self.results: - for message in self.results[reaction_name]: + if reaction_name in self._results: + for message in self._results[reaction_name]: if message['is_warning']: - self.num_warnings -= 1 + self._num_warnings -= 1 else: - self.num_errors -= 1 - del self.results[reaction_name] + self._num_errors -= 1 + del self._results[reaction_name] - def count_messages(self): """ - Returns the total number of errors and warnings. + Counts the total number of messages (errors and warnings). + + Returns: + int: The total number of messages. """ - return self.num_errors + self.num_warnings + return self._num_errors + self._num_warnings def count_errors(self): """ - Returns the total number of errors. + Counts the total number of errors. + + Returns: + int: The total number of errors. """ - return self.num_errors + return self._num_errors def count_warnings(self): """ - Returns the total number of warnings. + Counts the total number of warnings. + + Returns: + int: The total number of warnings. """ - return self.num_warnings + return self._num_warnings def __repr__(self): """ - Overrides the __repr__ method. + Returns a string representation of the results. Returns: - str: A string representation of the results. + str: A formatted string representing the results. """ if self.count_messages() == 0: return 'No errors or warnings found.' str_repr = '' - for reaction_name, messages in self.results.items(): + for reaction_name, messages in self._results.items(): str_repr += f'{reaction_name}:\n' for message in messages: is_warning = message['is_warning'] diff --git a/ratesb_python/common/util.py b/ratesb_python/common/util.py index 969f1b6..4a63185 100644 --- a/ratesb_python/common/util.py +++ b/ratesb_python/common/util.py @@ -40,10 +40,6 @@ def get_json_str(json_reference): else: raise ValueError("Invalid rate law file path.") return json_str - -def checkSBMLDocument(document): - if (document.getNumErrors() > 0): - print("SBML Document Error") def check_equal(expr1, expr2, n=4, sample_min=1, sample_max=10): """check if two sympy expressions are equal by plugging random numbers diff --git a/tests/test_analyzer.py b/tests/test_analyzer.py index 74fe024..3968b5a 100644 --- a/tests/test_analyzer.py +++ b/tests/test_analyzer.py @@ -8,6 +8,7 @@ sys.path.append(common_dir) from analyzer import Analyzer +from analyzer import check_model DIR = os.path.dirname(os.path.abspath(__file__)) @@ -139,6 +140,20 @@ def test_init(self): except ValueError: self.fail("Unexpected ValueError") + def test_check_model(self): + results = check_model(PATH_1) + self.assertEqual(str(results), '_J0:\n Warning 1006: Expecting these parameters to be constants: Km1\n') + + def test_list_all_checks(self): + checks = Analyzer.list_all_checks() + self.assertTrue(isinstance(checks, str)) + + def test_list_check(self): + check_1 = Analyzer.list_check(1) + self.assertEqual(check_1, "1: No Rate Law entered - The rate law is missing for the given reaction. Please provide a rate law to proceed.") + check_1001 = Analyzer.list_check(1001) + self.assertEqual(check_1001, "1001: Rate law contains only number - The rate law consists of only a numeric value. This is usually not a valid rate law for a reaction.") + def test_check_0001(self): true_case_analyzer = Analyzer(TRUE_PATH_2) false_case_analyzer = Analyzer(FALSE_PATH_2) diff --git a/tests/test_results.py b/tests/test_results.py index 99429c5..64287db 100644 --- a/tests/test_results.py +++ b/tests/test_results.py @@ -20,28 +20,28 @@ def test_add_message(self): self.results.add_message("reaction_name", 1, "message") self.assertEqual(self.results.count_errors(), 0) self.assertEqual(self.results.count_warnings(), 1) - self.assertEqual(self.results.results, {"reaction_name": [{"code": 1, "message": "message", "is_warning": True}]}) + self.assertEqual(self.results._results, {"reaction_name": [{"code": 1, "message": "message", "is_warning": True}]}) self.results.add_message("reaction_name", 2, "message", is_warning=False) self.assertEqual(self.results.count_errors(), 1) self.assertEqual(self.results.count_warnings(), 1) - self.assertEqual(self.results.results, {"reaction_name": [{"code": 1, "message": "message", "is_warning": True}, {"code": 2, "message": "message", "is_warning": False}]}) + self.assertEqual(self.results._results, {"reaction_name": [{"code": 1, "message": "message", "is_warning": True}, {"code": 2, "message": "message", "is_warning": False}]}) def test_clear_results(self): self.results.add_message("reaction_name", 1, "message") self.results.clear_results() self.assertEqual(self.results.count_errors(), 0) self.assertEqual(self.results.count_warnings(), 0) - self.assertEqual(self.results.results, {}) + self.assertEqual(self.results._results, {}) def test_get_warnings(self): self.results.add_message("reaction_name", 1, "message") self.results.add_message("reaction_name", 2, "message", is_warning=False) - self.assertEqual(self.results.get_warnings(), {"reaction_name": [{"code": 1, "message": "message", "is_warning": True}]}) + self.assertEqual(self.results.get_all_warnings(), {"reaction_name": [{"code": 1, "message": "message", "is_warning": True}]}) def test_get_errors(self): self.results.add_message("reaction_name", 1, "message") self.results.add_message("reaction_name", 2, "message", is_warning=False) - self.assertEqual(self.results.get_errors(), {"reaction_name": [{"code": 2, "message": "message", "is_warning": False}]}) + self.assertEqual(self.results.get_all_errors(), {"reaction_name": [{"code": 2, "message": "message", "is_warning": False}]}) def test_get_messages_by_reaction(self): self.results.add_message("reaction_name", 1, "message") @@ -55,7 +55,7 @@ def test_remove_messages_by_reaction(self): self.results.remove_messages_by_reaction("reaction_name") self.assertEqual(self.results.count_errors(), 0) self.assertEqual(self.results.count_warnings(), 0) - self.assertEqual(self.results.results, {}) + self.assertEqual(self.results._results, {}) def test_count_messages(self): self.results.add_message("reaction_name", 1, "message")