diff --git a/.github/workflows/python-lint.yml b/.github/workflows/python-lint.yml index b667148..13e9057 100644 --- a/.github/workflows/python-lint.yml +++ b/.github/workflows/python-lint.yml @@ -1,7 +1,7 @@ name: python lint on: [push] jobs: - sdk-python-lint: + python-lint: runs-on: ubuntu-latest steps: - name: git clone diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml new file mode 100644 index 0000000..192496e --- /dev/null +++ b/.github/workflows/python-test.yml @@ -0,0 +1,21 @@ +name: python test +on: [push] +jobs: + python-test: + runs-on: ubuntu-latest + steps: + - name: git clone + uses: actions/checkout@v3 + + - name: set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.12" + + - name: install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: unit tests + run: python -m unittest diff --git a/nextmv-py.code-workspace b/nextmv-py.code-workspace index 5709732..8234f0a 100644 --- a/nextmv-py.code-workspace +++ b/nextmv-py.code-workspace @@ -1,8 +1,12 @@ { - "folders": [ - { - "path": "." - } - ], - "settings": {} + "folders": [ + { + "path": "." + } + ], + "settings": { + "python.testing.unittestArgs": ["-v", "-s", "./tests", "-p", "test*.py"], + "python.testing.pytestEnabled": false, + "python.testing.unittestEnabled": true + } } diff --git a/nextmv/nextroute/schema/__init__.py b/nextmv/nextroute/schema/__init__.py index 9823e5f..c9080f6 100644 --- a/nextmv/nextroute/schema/__init__.py +++ b/nextmv/nextroute/schema/__init__.py @@ -1,6 +1,12 @@ """Schema (class) definitions for the entities in Nextroute.""" +from .input import Defaults as Defaults +from .input import DurationGroup as DurationGroup +from .input import Input as Input from .location import Location as Location +from .stop import AlternateStop as AlternateStop from .stop import Stop as Stop +from .stop import StopDefaults as StopDefaults from .vehicle import InitialStop as InitialStop from .vehicle import Vehicle as Vehicle +from .vehicle import VehicleDefaults as VehicleDefaults diff --git a/nextmv/nextroute/schema/base.py b/nextmv/nextroute/schema/base.py index 602b22b..b5bb0dd 100644 --- a/nextmv/nextroute/schema/base.py +++ b/nextmv/nextroute/schema/base.py @@ -1,5 +1,6 @@ """Base class for data wrangling.""" +import json from dataclasses import asdict, dataclass from typing import Any @@ -14,6 +15,15 @@ def from_dict(cls, data: dict[str, Any]): return cls(**data) + @classmethod + def from_json(cls, filepath: str): + """Instantiates the class from a JSON file.""" + + with open(filepath) as f: + data = json.load(f) + + return cls.from_dict(data) + def to_dict(self) -> dict[str, Any]: """Converts the class to a dict.""" diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/nextroute/__init__.py b/tests/nextroute/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/nextroute/schema/__init__.py b/tests/nextroute/schema/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/nextroute/schema/input.json b/tests/nextroute/schema/input.json new file mode 100644 index 0000000..bfb9969 --- /dev/null +++ b/tests/nextroute/schema/input.json @@ -0,0 +1,263 @@ +{ + "defaults": { + "vehicles": { + "capacity": { + "bunnies": 20, + "rabbits": 10 + }, + "start_location": { + "lat": 35.791729813680874, + "lon": -78.7401685145487 + }, + "end_location": { + "lat": 35.791729813680874, + "lon": -78.7401685145487 + }, + "speed": 10 + }, + "stops": { + "duration": 300, + "quantity": { + "bunnies": -1, + "rabbits": -1 + }, + "unplanned_penalty": 200000, + "target_arrival_time": "2023-01-01T10:00:00Z", + "early_arrival_time_penalty": 1.5, + "late_arrival_time_penalty": 1.5 + } + }, + "stops": [ + { + "id": "s1", + "location": { + "lon": -78.90919, + "lat": 35.72389 + }, + "compatibility_attributes": ["premium"] + }, + { + "id": "s2", + "location": { + "lon": -78.813862, + "lat": 35.75712 + }, + "compatibility_attributes": ["premium"] + }, + { + "id": "s3", + "location": { + "lon": -78.92996, + "lat": 35.932795 + }, + "compatibility_attributes": ["premium"] + }, + { + "id": "s4", + "location": { + "lon": -78.505745, + "lat": 35.77772 + }, + "compatibility_attributes": ["premium"] + }, + { + "id": "s5", + "location": { + "lon": -78.75084, + "lat": 35.732995 + }, + "compatibility_attributes": ["premium"] + }, + { + "id": "s6", + "location": { + "lon": -78.788025, + "lat": 35.813025 + }, + "compatibility_attributes": ["premium"] + }, + { + "id": "s7", + "location": { + "lon": -78.749391, + "lat": 35.74261 + }, + "compatibility_attributes": ["premium"] + }, + { + "id": "s8", + "location": { + "lon": -78.94658, + "lat": 36.039135 + }, + "compatibility_attributes": ["basic"] + }, + { + "id": "s9", + "location": { + "lon": -78.64972, + "lat": 35.64796 + }, + "compatibility_attributes": ["basic"] + }, + { + "id": "s10", + "location": { + "lon": -78.747955, + "lat": 35.672955 + }, + "compatibility_attributes": ["basic"] + }, + { + "id": "s11", + "location": { + "lon": -78.83403, + "lat": 35.77013 + }, + "compatibility_attributes": ["basic"] + }, + { + "id": "s12", + "location": { + "lon": -78.864465, + "lat": 35.782855 + }, + "compatibility_attributes": ["basic"] + }, + { + "id": "s13", + "location": { + "lon": -78.952142, + "lat": 35.88029 + }, + "compatibility_attributes": ["basic"] + }, + { + "id": "s14", + "location": { + "lon": -78.52748, + "lat": 35.961465 + }, + "compatibility_attributes": ["basic"] + }, + { + "id": "s15", + "location": { + "lon": -78.89832, + "lat": 35.83202 + } + }, + { + "id": "s16", + "location": { + "lon": -78.63216, + "lat": 35.83458 + } + }, + { + "id": "s17", + "location": { + "lon": -78.76063, + "lat": 35.67337 + } + }, + { + "id": "s18", + "location": { + "lon": -78.911485, + "lat": 36.009015 + } + }, + { + "id": "s19", + "location": { + "lon": -78.522705, + "lat": 35.93663 + } + }, + { + "id": "s20", + "location": { + "lon": -78.995162, + "lat": 35.97414 + } + }, + { + "id": "s21", + "location": { + "lon": -78.50509, + "lat": 35.7606 + } + }, + { + "id": "s22", + "location": { + "lon": -78.828547, + "lat": 35.962635 + }, + "precedes": ["s16", "s23"] + }, + { + "id": "s23", + "location": { + "lon": -78.60914, + "lat": 35.84616 + }, + "start_time_window": [ + "2023-01-01T09:00:00-06:00", + "2023-01-01T09:30:00-06:00" + ] + }, + { + "id": "s24", + "location": { + "lon": -78.65521, + "lat": 35.740605 + }, + "start_time_window": [ + "2023-01-01T09:00:00-06:00", + "2023-01-01T09:30:00-06:00" + ], + "succeeds": "s25" + }, + { + "id": "s25", + "location": { + "lon": -78.92051, + "lat": 35.887575 + }, + "start_time_window": [ + "2023-01-01T09:00:00-06:00", + "2023-01-01T09:30:00-06:00" + ], + "precedes": "s26" + }, + { + "id": "s26", + "location": { + "lon": -78.84058, + "lat": 35.823865 + }, + "start_time_window": [ + "2023-01-01T09:00:00-06:00", + "2023-01-01T09:30:00-06:00" + ] + } + ], + "vehicles": [ + { + "id": "vehicle-0", + "start_time": "2023-01-01T06:00:00-06:00", + "end_time": "2023-01-01T10:00:00-06:00", + "activation_penalty": 4000, + "compatibility_attributes": ["premium"] + }, + { + "id": "vehicle-1", + "start_time": "2023-01-01T10:00:00-06:00", + "end_time": "2023-01-01T16:00:00-06:00", + "max_duration": 21000, + "compatibility_attributes": ["basic"] + } + ] +} diff --git a/tests/nextroute/schema/test_input.py b/tests/nextroute/schema/test_input.py new file mode 100644 index 0000000..51e3ae4 --- /dev/null +++ b/tests/nextroute/schema/test_input.py @@ -0,0 +1,21 @@ +import json +import os +import unittest + +from nextmv.nextroute.schema import Input + + +class TestInput(unittest.TestCase): + def test_from_json(self): + filepath = os.path.join(os.path.dirname(__file__), "input.json") + input = Input.from_json(filepath) + parsed = input.to_dict() + with open(filepath) as f: + expected = json.load(f) + + self.maxDiff = None + self.assertEqual( + parsed, + expected, + "Parsing the JSON into the class and back should yield the same result.", + ) diff --git a/tests/test_hello.py b/tests/test_hello.py deleted file mode 100644 index 43aba5b..0000000 --- a/tests/test_hello.py +++ /dev/null @@ -1,10 +0,0 @@ -import nextmv - - -def test_hello(): - nextmv.main() - - -if __name__ == "__main__": - test_hello() - print("Everything passed")