diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 57f7ae5..1f2e8e5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -27,7 +27,7 @@ jobs: - name: Extract functionInputSchema id: extract_schema run: | - python main.py generate_schema ${HOME}/${{ env.FUNCTION_SCHEMA_FILE_NAME }} + python src/main.py generate_schema ${HOME}/${{ env.FUNCTION_SCHEMA_FILE_NAME }} - name: Speckle Automate Function - Build and Publish uses: specklesystems/speckle-automate-github-composite-action@0.8.1 with: @@ -35,6 +35,6 @@ jobs: speckle_token: ${{ secrets.SPECKLE_FUNCTION_TOKEN }} speckle_function_id: ${{ secrets.SPECKLE_FUNCTION_ID }} speckle_function_input_schema_file_path: ${{ env.FUNCTION_SCHEMA_FILE_NAME }} - speckle_function_command: 'python -u main.py run' + speckle_function_command: 'python -u src/main.py run' speckle_function_recommended_cpu_m: 4000 speckle_function_recommended_memory_mi: 4000 diff --git a/pyproject.toml b/pyproject.toml index a161c50..84d0d0d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,6 +4,7 @@ version = "0.1.0" description = "Examine model health by identifying areas of high mesh density as possible perfomance issues." authors = ["Jonathon Broughton "] readme = "README.md" +packages = [{ include = "src" }] [tool.poetry.dependencies] python = "^3.11" @@ -25,6 +26,7 @@ requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" [tool.ruff] +line-length = 88 select = [ "E", # pycodestyle "F", # pyflakes @@ -34,4 +36,4 @@ select = [ ] [tool.ruff.pydocstyle] -convention = "google" +convention = "google" \ No newline at end of file diff --git a/Objects/__init__.py b/src/Objects/__init__.py similarity index 100% rename from Objects/__init__.py rename to src/Objects/__init__.py diff --git a/Objects/objects.py b/src/Objects/objects.py similarity index 99% rename from Objects/objects.py rename to src/Objects/objects.py index f04c185..27d6b27 100644 --- a/Objects/objects.py +++ b/src/Objects/objects.py @@ -1,4 +1,3 @@ -import json import statistics from dataclasses import dataclass, field from typing import Any, Dict, Iterable, List, Optional, TypeVar, Union @@ -11,7 +10,7 @@ from specklepy.objects.other import RenderMaterial from specklepy.objects.primitive import Interval -from Utilities.utilities import Utilities +from src.Utilities import Utilities T = TypeVar("T", bound=Base) diff --git a/Utilities/__init__.py b/src/Utilities/__init__.py similarity index 100% rename from Utilities/__init__.py rename to src/Utilities/__init__.py diff --git a/Utilities/flatten.py b/src/Utilities/flatten.py similarity index 100% rename from Utilities/flatten.py rename to src/Utilities/flatten.py diff --git a/Utilities/plotting.py b/src/Utilities/plotting.py similarity index 99% rename from Utilities/plotting.py rename to src/Utilities/plotting.py index c4fd2e3..64f6a6a 100644 --- a/Utilities/plotting.py +++ b/src/Utilities/plotting.py @@ -127,4 +127,3 @@ def plot_area_distribution(areas: List[float]) -> None: plt.ylabel("Count") plt.title("Area Distribution") plt.grid(True) - # plt.show() diff --git a/Utilities/reporting.py b/src/Utilities/reporting.py similarity index 89% rename from Utilities/reporting.py rename to src/Utilities/reporting.py index d1a3087..c9a7354 100644 --- a/Utilities/reporting.py +++ b/src/Utilities/reporting.py @@ -1,10 +1,10 @@ import io -import os import tempfile from datetime import datetime from pathlib import Path from typing import IO, Any, Dict, List, Union +import httpx import matplotlib.pyplot as plt from PIL import Image as PILImage from reportlab.lib.colors import green, red @@ -21,8 +21,8 @@ TableStyle, ) -from Objects.objects import HealthObject -from Utilities.plotting import Plotting +from src.Objects.objects import HealthObject +from src.Utilities.plotting import Plotting class Report: @@ -260,3 +260,32 @@ def write_pdf_to_temp(report: IO[bytes]) -> str: temp_file.write_bytes(report.read()) return str(temp_file) + + +from speckle_automate import AutomationContext + + +def safe_store_file_result(automate_context: AutomationContext, file_name: str): + # Store the original URL + original_url = automate_context.automation_run_data.speckle_server_url + + try: + # Modify the URL property of the automation_run_data + automate_context.automation_run_data.speckle_server_url = original_url.rstrip( + "/" + ) + + # Attempt to store the file + automate_context.store_file_result(file_name) + except httpx.HTTPStatusError as e: + if e.response.status_code == 404: + # Handle the 404 error + error_message = f"Unable to store file: {file_name}. Error: {str(e)}" + print(error_message) # For logging purposes + automate_context.mark_run_exception(error_message) + + else: + raise + finally: + # Restore the original URL + automate_context.automation_run_data.speckle_server_url = original_url diff --git a/Utilities/utilities.py b/src/Utilities/utilities.py similarity index 96% rename from Utilities/utilities.py rename to src/Utilities/utilities.py index e0d12b4..0421d9a 100644 --- a/Utilities/utilities.py +++ b/src/Utilities/utilities.py @@ -1,9 +1,9 @@ -from typing import List, TypeVar, Iterable, Optional, Tuple +from typing import List, TypeVar, Iterable, Optional from specklepy.objects.base import Base import sys -from Utilities.flatten import extract_base_and_transform +from src.Utilities.flatten import extract_base_and_transform T = TypeVar("T", bound=Base) diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/main.py b/src/main.py similarity index 91% rename from main.py rename to src/main.py index 196a564..44f1a19 100644 --- a/main.py +++ b/src/main.py @@ -1,8 +1,10 @@ -"""This module contains the business logic for a Speckle Automate function. +( + """This module contains the business logic for a Speckle Automate function. -The purpose is to demonstrate how one can use the automation_context module -to process and analyze data in a Speckle project. -""" + The purpose is to demonstrate how one can use the automation_context module + to process and analyze data in a Speckle project. + """ +) from pydantic import Field from speckle_automate import ( AutomateBase, @@ -10,7 +12,6 @@ execute_automate_function, ) -import Objects.objects from Objects.objects import ( attach_visual_markers, colorise_densities, @@ -25,6 +26,7 @@ ## swap those into the original commit object ## send that back to the server + class FunctionInputs(AutomateBase): """Definition of user inputs for this function. @@ -53,7 +55,7 @@ class FunctionInputs(AutomateBase): def automate_function( - automate_context: AutomationContext, function_inputs: FunctionInputs + automate_context: AutomationContext, function_inputs: FunctionInputs ) -> None: """Analyzes Speckle data and provides visual markers and notifications. @@ -84,6 +86,10 @@ def automate_function( automate_context, health_objects, function_inputs.density_level ) + + + + colorise_densities(automate_context, health_objects) # Wrap up the analysis by marking the run either successful or failed. @@ -95,7 +101,9 @@ def automate_function( commit_details = { "stream_id": automate_context.automation_run_data.project_id, - "commit_id": automate_context.automation_run_data.triggers[0].payload.version_id, + "commit_id": automate_context.automation_run_data.triggers[ + 0 + ].payload.version_id, "server_url": automate_context.automation_run_data.speckle_server_url, } @@ -117,12 +125,12 @@ def automate_function( file_name = Report.write_pdf_to_temp(report) - print(commit_details['server_url']) + print(commit_details["server_url"]) automate_context.store_file_result(file_name) # colorise the objects that pass/fail and send to a new model version - Objects.objects.transport_recolorized_commit( + src.Objects.objects.transport_recolorized_commit( automate_context, health_objects, version_root_object ) diff --git a/tests/conftest.py b/tests/conftest.py index b776bd6..b0ff167 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,8 @@ import os +import sys from dotenv import load_dotenv +# Add the src directory to the Python path +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../src'))) def pytest_configure(config): diff --git a/tests/test_function.py b/tests/test_function.py index a754d6e..e80100d 100644 --- a/tests/test_function.py +++ b/tests/test_function.py @@ -14,7 +14,7 @@ from specklepy.api.client import SpeckleClient from specklepy.objects.base import Base -from main import FunctionInputs, automate_function +from src.main import FunctionInputs, automate_function def crypto_random_string(length: int) -> str: @@ -94,7 +94,6 @@ def test_object() -> Base: return root_object - @pytest.fixture() # fixture to mock the AutomationRunData that would be generated by a full Automation Run def fake_automation_run_data(request, test_client: SpeckleClient) -> AutomationRunData: diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 84f5389..1937060 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -1,8 +1,8 @@ import pytest from specklepy.objects.base import Base -from Objects.objects import HealthObject -from Utilities.utilities import Utilities +from src.Objects import HealthObject +from src.Utilities import Utilities @pytest.fixture @@ -39,7 +39,7 @@ def test_filter_displayable_bases(mock_base): displayable_bases = Utilities.filter_displayable_bases(mock_base) assert ( len(displayable_bases) == 2 - ) # Only child_1 and child_2 should be considered displayable + ) def test_convert_from_base_with_nested_elements(mock_base): @@ -48,7 +48,7 @@ def test_convert_from_base_with_nested_elements(mock_base): assert health_obj.id == "12345" assert ( health_obj.speckle_type == "Base" - ) # Assuming no speckle_type was set in the mock_base + ) def test_density_with_nested_elements(mock_base): diff --git a/tests/test_local.py b/tests/test_local.py index d66fa88..4e6f681 100644 --- a/tests/test_local.py +++ b/tests/test_local.py @@ -2,13 +2,13 @@ from typing import Any import pytest - # Speckle is a data platform for AEC; here we're importing essential modules from it from specklepy.api import operations from specklepy.api.client import SpeckleClient from specklepy.objects import Base from specklepy.transports.server import ServerTransport + # Setting up some pytest fixtures for testing # Fixtures are a way to provide consistent test data or configuration for each test diff --git a/tests/test_utilities.py b/tests/test_utilities.py index dc8e025..1d0a082 100644 --- a/tests/test_utilities.py +++ b/tests/test_utilities.py @@ -2,7 +2,7 @@ from specklepy.objects.base import Base from specklepy.objects.geometry import Mesh -from Utilities.utilities import Utilities +from src.Utilities import Utilities @pytest.fixture