From ae871db48857b78d4907a1ed58bb8b07af9666e8 Mon Sep 17 00:00:00 2001 From: CoreRasurae Date: Mon, 5 Jan 2026 21:39:25 +0000 Subject: [PATCH] feat: Update to new CleverSwarm ontology loading capabilities ISSUES CLOSED: #8 --- .../cswarm_text_to_kg_client.py | 35 ++++----- .../libs/cleverswarm_client.py | 32 ++++---- tests/test_cswarm_text_to_kg_client.py | 73 +++++++++++++++++-- 3 files changed, 92 insertions(+), 48 deletions(-) diff --git a/src/cleverswarm_python_client/cswarm_text_to_kg_client.py b/src/cleverswarm_python_client/cswarm_text_to_kg_client.py index 184e041..47a433c 100644 --- a/src/cleverswarm_python_client/cswarm_text_to_kg_client.py +++ b/src/cleverswarm_python_client/cswarm_text_to_kg_client.py @@ -76,7 +76,10 @@ class TextoToKGCLI(object): force_filetype: FileTypeAPI = FileTypeAPI.AutoDetect, ): input_text_path: Path = self._input_prefix / input_text - ontology_json_path: Path = self._input_prefix / ontology_json + # Ontology JSON file is optional - only create path if it was provided + ontology_json_path: Path | None = ( + self._input_prefix / ontology_json if ontology_json else None + ) ontology_owl_path: Path = self._input_prefix / ontology_owl wildcards_path: Path = Path(wildcards) if wildcards else None @@ -85,11 +88,15 @@ class TextoToKGCLI(object): f"Unstructured input text does not exist or is not a file: {str(input_text_path)}" ) - if not ontology_json_path.exists() or not ontology_json_path.is_file(): + # If ontology_json was provided, validate that the file exists + if ontology_json and ontology_json_path and (not ontology_json_path.exists() or not ontology_json_path.is_file()): raise ClientException( - f"Input ontology JSON file does not exist or is not a file: {str(ontology_json_path)}" + f"Specified optional Ontology JSON file does not exist or is not a file: {str(ontology_json_path)}" ) + # Ontology JSON file is optional - use the path if it was provided and exists + ontology_json_path_final: Path | None = ontology_json_path + if not ontology_owl_path.exists() or not ontology_owl_path.is_file(): raise ClientException( f"Input ontology OWL file does not exist or is not a file: {str(ontology_owl_path)}" @@ -106,7 +113,7 @@ class TextoToKGCLI(object): if wildcards_job: job_id: str = self._client.create_unstructured_to_kg_wildcards_job( input_text_path, - ontology_json_path, + ontology_json_path_final, ontology_owl_path, wildcards_path, force_filetype=force_filetype, @@ -114,7 +121,7 @@ class TextoToKGCLI(object): else: job_id: str = self._client.create_unstructured_to_kg_job( input_text_path, - ontology_json_path, + ontology_json_path_final, ontology_owl_path, force_filetype=force_filetype, ) @@ -272,7 +279,7 @@ def main(): parser.add_argument( "--ontology_json", type=str, - help="Enriched ontology file with descriptions in JSON format", + help="Optional enriched ontology file with descriptions in JSON format", ) parser.add_argument( "--ontology_owl", @@ -306,10 +313,6 @@ def main(): print("create-and-exit action requires --unstructured_text argument.\n") parser.print_help() exit(-1) - if args.ontology_json is None: - print("create-and-exit action requires --ontology_json argument.\n") - parser.print_help() - exit(-1) if args.ontology_owl is None: print("create-and-exit action requires --ontology_owl argument.\n") parser.print_help() @@ -339,12 +342,6 @@ def main(): ) parser.print_help() exit(-1) - if args.ontology_json is None: - print( - "create-download-kg-delete action requires --ontology_json argument.\n" - ) - parser.print_help() - exit(-1) if args.ontology_owl is None: print( "create-download-kg-delete action requires --ontology_owl argument.\n" @@ -361,12 +358,6 @@ def main(): ) parser.print_help() exit(-1) - if args.ontology_json is None: - print( - "create-download-kg-delete action requires --ontology_json argument.\n" - ) - parser.print_help() - exit(-1) if args.ontology_owl is None: print( "create-download-kg-delete action requires --ontology_owl argument.\n" diff --git a/src/cleverswarm_python_client/libs/cleverswarm_client.py b/src/cleverswarm_python_client/libs/cleverswarm_client.py index 6fe5d43..48ade94 100644 --- a/src/cleverswarm_python_client/libs/cleverswarm_client.py +++ b/src/cleverswarm_python_client/libs/cleverswarm_client.py @@ -595,7 +595,7 @@ class CleverSwarmClient(object): def create_unstructured_to_kg_job( self, unstructured_text_file: Path, - ontology_file: Path, + ontology_file: Path | None, ontology_spec_file: Path, force_filetype: FileTypeAPI = FileTypeAPI.AutoDetect, ): @@ -604,7 +604,7 @@ class CleverSwarmClient(object): Login is requested if required by the server. If the upload is successful, it returns a job ID. :param unstructured_text_file: a valid file path containing unstructured text for conversion - :param ontology_file: a valid file containing an ontology in JSON format. + :param ontology_file: optional file containing an ontology in JSON format. :param ontology_spec_file: a valid file path containing the ontology spec in OWL/XML format. :param force_filetype: indicate the file type to force for the input file :return: the job ID where the files were placed. @@ -618,11 +618,6 @@ class CleverSwarmClient(object): f"{str(unstructured_text_file)}" ) - if not ontology_file.exists() or not ontology_file.is_file(): - raise InvalidFileException( - f"Ontology files does not exist or is not a file: {str(ontology_file)}" - ) - if not ontology_spec_file.exists() or not ontology_spec_file.is_file(): raise InvalidFileException( f"OWL/XML ontology file does not exist or is not a file: " @@ -634,11 +629,14 @@ class CleverSwarmClient(object): params = {"file_type": force_filetype} files = [ ("unstructured", open(unstructured_text_file, "rb")), - ("ontology", open(ontology_file, "rb")), ("ontology_spec", open(ontology_spec_file, "rb")), ] + if ontology_file: + files.append(("ontology", open(ontology_file, "rb"))) + response = requests.post( - self._base_url + "unstructured/with_ontology", + self._base_url + ("unstructured/with_ontology" if ontology_file else + "unstructured/with_ontology_simple"), headers=headers, params=params, files=files, @@ -669,7 +667,7 @@ class CleverSwarmClient(object): def create_unstructured_to_kg_wildcards_job( self, unstructured_text_file: Path, - ontology_file: Path, + ontology_file: Path | None, ontology_spec_file: Path, wildcards_query_file: Path, force_filetype: FileTypeAPI = FileTypeAPI.AutoDetect, @@ -679,7 +677,7 @@ class CleverSwarmClient(object): Login is requested if required by the server. If the upload is successful, it returns a job ID. :param unstructured_text_file: a valid file path containing unstructured text for conversion - :param ontology_file: a valid file containing an ontology in JSON format. + :param ontology_file: optional file containing an ontology in JSON format. :param ontology_spec_file: a valid file path containing the ontology spec in OWL/XML format. :param wildcards_query_file: a valid file path containing wildcard queries in JSON. :param force_filetype: indicate the file type to force for the input file @@ -694,11 +692,6 @@ class CleverSwarmClient(object): f"{str(unstructured_text_file)}" ) - if not ontology_file.exists() or not ontology_file.is_file(): - raise InvalidFileException( - f"Ontology files does not exist or is not a file: {str(ontology_file)}" - ) - if not ontology_spec_file.exists() or not ontology_spec_file.is_file(): raise InvalidFileException( f"OWL/XML ontology file does not exist or is not a file: " @@ -716,12 +709,15 @@ class CleverSwarmClient(object): params = {"file_type": force_filetype} files = [ ("unstructured", open(unstructured_text_file, "rb")), - ("ontology", open(ontology_file, "rb")), ("ontology_spec", open(ontology_spec_file, "rb")), ("wildcards", open(wildcards_query_file, "rb")), ] + if ontology_file: + files.append(("ontology", open(ontology_file, "rb"))) + response = requests.post( - self._base_url + "unstructured/with_wildcards", + self._base_url + ("unstructured/with_wildcards" if ontology_file else + "unstructured/with_wildcards_simple"), headers=headers, params=params, files=files, diff --git a/tests/test_cswarm_text_to_kg_client.py b/tests/test_cswarm_text_to_kg_client.py index 94fedbe..6d58387 100644 --- a/tests/test_cswarm_text_to_kg_client.py +++ b/tests/test_cswarm_text_to_kg_client.py @@ -214,18 +214,75 @@ class TestTextoToKGCLIJobCreation: "test.owl" ) - def test_create_job_invalid_json_file(self): - """Test job creation with invalid JSON file.""" + def test_create_job_with_invalid_json_file(self): + """Test job creation with invalid (non-existent) JSON file - should raise exception if JSON is provided.""" with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.txt') as text_file: text_file.write("Test text content") text_file.flush() - with pytest.raises(ClientException, match="Input ontology JSON file does not exist"): - self.cli.create_job( - text_file.name, - "nonexistent.json", - "test.owl" - ) + with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.owl') as owl_file: + owl_file.write('test') + owl_file.flush() + + # If ontology_json is provided but file doesn't exist, should raise exception + with pytest.raises(ClientException, match="Specified optional Ontology JSON file does not exist"): + self.cli.create_job( + text_file.name, + "nonexistent.json", # Non-existent JSON file - should raise exception + owl_file.name + ) + + def test_create_job_without_json_file(self): + """Test job creation without JSON file - should work since JSON is optional.""" + with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.txt') as text_file: + text_file.write("Test text content") + text_file.flush() + + with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.owl') as owl_file: + owl_file.write('test') + owl_file.flush() + + with patch.object(self.cli._client, 'create_unstructured_to_kg_job') as mock_create: + mock_create.return_value = "test_job_123" + + # Job creation should work without JSON file since it's optional + result = self.cli.create_job( + text_file.name, + None, # No JSON file provided - should work + owl_file.name + ) + + assert result == "test_job_123" + mock_create.assert_called_once() + # Verify that None was passed for ontology_file since it wasn't provided + call_args = mock_create.call_args + assert call_args[0][1] is None # ontology_file parameter should be None + + def test_create_job_with_empty_json_file_string(self): + """Test job creation with empty string for JSON file - should work since JSON is optional.""" + with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.txt') as text_file: + text_file.write("Test text content") + text_file.flush() + + with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.owl') as owl_file: + owl_file.write('test') + owl_file.flush() + + with patch.object(self.cli._client, 'create_unstructured_to_kg_job') as mock_create: + mock_create.return_value = "test_job_123" + + # Job creation should work with empty string for JSON file since it's optional + result = self.cli.create_job( + text_file.name, + "", # Empty string for JSON file - should work + owl_file.name + ) + + assert result == "test_job_123" + mock_create.assert_called_once() + # Verify that None was passed for ontology_file since empty string is falsy + call_args = mock_create.call_args + assert call_args[0][1] is None # ontology_file parameter should be None def test_create_job_invalid_owl_file(self): """Test job creation with invalid OWL file."""