nextmv 0.1.0.dev17__tar.gz → 0.2.0.dev0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. {nextmv-0.1.0.dev17 → nextmv-0.2.0.dev0}/.github/workflows/publish.yml +57 -22
  2. {nextmv-0.1.0.dev17 → nextmv-0.2.0.dev0}/.gitignore +3 -0
  3. {nextmv-0.1.0.dev17 → nextmv-0.2.0.dev0}/PKG-INFO +8 -6
  4. {nextmv-0.1.0.dev17 → nextmv-0.2.0.dev0}/README.md +6 -5
  5. nextmv-0.2.0.dev0/nextmv/__about__.py +1 -0
  6. {nextmv-0.1.0.dev17 → nextmv-0.2.0.dev0}/nextmv/cloud/application.py +5 -2
  7. {nextmv-0.1.0.dev17 → nextmv-0.2.0.dev0}/nextmv/cloud/client.py +46 -15
  8. {nextmv-0.1.0.dev17 → nextmv-0.2.0.dev0}/pyproject.toml +1 -0
  9. {nextmv-0.1.0.dev17 → nextmv-0.2.0.dev0}/requirements.txt +1 -0
  10. nextmv-0.2.0.dev0/tests/cloud/test_client.py +25 -0
  11. nextmv-0.2.0.dev0/tests/nextroute/schema/__init__.py +0 -0
  12. nextmv-0.1.0.dev17/nextmv/__about__.py +0 -1
  13. {nextmv-0.1.0.dev17 → nextmv-0.2.0.dev0}/.github/workflows/python-lint.yml +0 -0
  14. {nextmv-0.1.0.dev17 → nextmv-0.2.0.dev0}/.github/workflows/python-test.yml +0 -0
  15. {nextmv-0.1.0.dev17 → nextmv-0.2.0.dev0}/LICENSE +0 -0
  16. {nextmv-0.1.0.dev17 → nextmv-0.2.0.dev0}/nextmv/__init__.py +0 -0
  17. {nextmv-0.1.0.dev17 → nextmv-0.2.0.dev0}/nextmv/base_model.py +0 -0
  18. {nextmv-0.1.0.dev17 → nextmv-0.2.0.dev0}/nextmv/cloud/__init__.py +0 -0
  19. {nextmv-0.1.0.dev17 → nextmv-0.2.0.dev0}/nextmv/cloud/acceptance_test.py +0 -0
  20. {nextmv-0.1.0.dev17 → nextmv-0.2.0.dev0}/nextmv/cloud/batch_experiment.py +0 -0
  21. {nextmv-0.1.0.dev17 → nextmv-0.2.0.dev0}/nextmv/cloud/input_set.py +0 -0
  22. {nextmv-0.1.0.dev17 → nextmv-0.2.0.dev0}/nextmv/nextroute/__init__.py +0 -0
  23. {nextmv-0.1.0.dev17 → nextmv-0.2.0.dev0}/nextmv/nextroute/check/__init__.py +0 -0
  24. {nextmv-0.1.0.dev17 → nextmv-0.2.0.dev0}/nextmv/nextroute/check/schema.py +0 -0
  25. {nextmv-0.1.0.dev17 → nextmv-0.2.0.dev0}/nextmv/nextroute/schema/__init__.py +0 -0
  26. {nextmv-0.1.0.dev17 → nextmv-0.2.0.dev0}/nextmv/nextroute/schema/input.py +0 -0
  27. {nextmv-0.1.0.dev17 → nextmv-0.2.0.dev0}/nextmv/nextroute/schema/location.py +0 -0
  28. {nextmv-0.1.0.dev17 → nextmv-0.2.0.dev0}/nextmv/nextroute/schema/output.py +0 -0
  29. {nextmv-0.1.0.dev17 → nextmv-0.2.0.dev0}/nextmv/nextroute/schema/stop.py +0 -0
  30. {nextmv-0.1.0.dev17 → nextmv-0.2.0.dev0}/nextmv/nextroute/schema/vehicle.py +0 -0
  31. {nextmv-0.1.0.dev17 → nextmv-0.2.0.dev0}/nextmv-py.code-workspace +0 -0
  32. {nextmv-0.1.0.dev17 → nextmv-0.2.0.dev0}/tests/__init__.py +0 -0
  33. {nextmv-0.1.0.dev17/tests/nextroute → nextmv-0.2.0.dev0/tests/cloud}/__init__.py +0 -0
  34. {nextmv-0.1.0.dev17/tests/nextroute/schema → nextmv-0.2.0.dev0/tests/nextroute}/__init__.py +0 -0
  35. {nextmv-0.1.0.dev17 → nextmv-0.2.0.dev0}/tests/nextroute/schema/input.json +0 -0
  36. {nextmv-0.1.0.dev17 → nextmv-0.2.0.dev0}/tests/nextroute/schema/output.json +0 -0
  37. {nextmv-0.1.0.dev17 → nextmv-0.2.0.dev0}/tests/nextroute/schema/output_with_check.json +0 -0
  38. {nextmv-0.1.0.dev17 → nextmv-0.2.0.dev0}/tests/nextroute/schema/test_input.py +0 -0
  39. {nextmv-0.1.0.dev17 → nextmv-0.2.0.dev0}/tests/nextroute/schema/test_output.py +0 -0
  40. {nextmv-0.1.0.dev17 → nextmv-0.2.0.dev0}/tests/test_base_model.py +0 -0
@@ -12,16 +12,15 @@ on:
12
12
  default: false
13
13
  type: boolean
14
14
 
15
- permissions:
16
- contents: write
17
-
18
15
  jobs:
19
- publish:
16
+ bump: # This job is used to bump the version and create a release
20
17
  runs-on: ubuntu-latest
21
18
  env:
22
19
  VERSION: ${{ inputs.VERSION }}
23
20
  GH_TOKEN: ${{ github.token }}
24
21
  SSH_AUTH_SOCK: /tmp/ssh_agent.sock
22
+ permissions:
23
+ contents: write
25
24
  steps:
26
25
  - name: set up Python
27
26
  uses: actions/setup-python@v4
@@ -31,7 +30,7 @@ jobs:
31
30
  - name: install dependencies
32
31
  run: |
33
32
  pip install --upgrade pip
34
- pip install build twine hatch
33
+ pip install build hatch
35
34
 
36
35
  - name: configure git with the bot credentials
37
36
  run: |
@@ -40,8 +39,13 @@ jobs:
40
39
  ssh-agent -a $SSH_AUTH_SOCK > /dev/null
41
40
  ssh-add - <<< "${{ secrets.NEXTMVBOT_SSH_KEY }}"
42
41
 
42
+ echo "${{ secrets.NEXTMVBOT_SIGNING_KEY }}" > ~/.ssh/signing.key
43
+ chmod 600 ~/.ssh/signing.key
44
+
43
45
  git config --global user.name "nextmv-bot"
44
46
  git config --global user.email "tech+gh-nextmv-bot@nextmv.io"
47
+ git config --global gpg.format ssh
48
+ git config --global user.signingkey ~/.ssh/signing.key
45
49
 
46
50
  git clone git@github.com:nextmv-io/nextmv-py.git
47
51
 
@@ -52,7 +56,7 @@ jobs:
52
56
  - name: commit new version
53
57
  run: |
54
58
  git add nextmv/__about__.py
55
- git commit -m "Bump version to $VERSION"
59
+ git commit -S -m "Bump version to $VERSION"
56
60
  git push
57
61
  git tag $VERSION
58
62
  git push origin $VERSION
@@ -71,23 +75,54 @@ jobs:
71
75
  --title $VERSION $PRERELEASE_FLAG
72
76
  working-directory: ./nextmv-py
73
77
 
74
- - name: build binary wheel and source tarball
75
- env:
76
- TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
77
- TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
78
+ - name: ensure passing build
78
79
  run: python -m build
79
80
  working-directory: ./nextmv-py
80
81
 
81
- - name: publish to TestPyPI
82
- env:
83
- TWINE_USERNAME: ${{ secrets.TESTPYPI_USERNAME }}
84
- TWINE_PASSWORD: ${{ secrets.TESTPYPI_PASSWORD }}
85
- run: twine upload --repository testpypi dist/*
86
- working-directory: ./nextmv-py
82
+ publish: # This job is used to publish the release to PyPI/TestPyPI
83
+ runs-on: ubuntu-latest
84
+ needs: bump
85
+ strategy:
86
+ matrix:
87
+ include:
88
+ - target-env: pypi
89
+ target-url: https://pypi.org/p/nextmv
90
+ - target-env: testpypi
91
+ target-url: https://test.pypi.org/p/nextmv
92
+ environment:
93
+ name: ${{ matrix.target-env }}
94
+ url: ${{ matrix.target-url }}
95
+ permissions:
96
+ contents: read
97
+ id-token: write # This is required for trusted publishing to PyPI
98
+ steps:
99
+ - name: git clone develop
100
+ uses: actions/checkout@v4
101
+ with:
102
+ ref: develop
87
103
 
88
- - name: publish to PyPI
89
- env:
90
- TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
91
- TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
92
- run: twine upload dist/*
93
- working-directory: ./nextmv-py
104
+ - name: set up Python
105
+ uses: actions/setup-python@v4
106
+ with:
107
+ python-version: "3.12"
108
+
109
+ - name: install dependencies
110
+ run: |
111
+ pip install --upgrade pip
112
+ pip install build hatch
113
+
114
+ - name: build binary wheel and source tarball
115
+ run: python -m build
116
+
117
+ - name: Publish package distributions to PyPI
118
+ if: ${{ matrix.target-env == 'pypi' }}
119
+ uses: pypa/gh-action-pypi-publish@release/v1
120
+ with:
121
+ packages-dir: ./dist
122
+
123
+ - name: Publish package distributions to TestPyPI
124
+ if: ${{ matrix.target-env == 'testpypi' }}
125
+ uses: pypa/gh-action-pypi-publish@release/v1
126
+ with:
127
+ repository-url: https://test.pypi.org/legacy/
128
+ packages-dir: ./dist
@@ -158,3 +158,6 @@ cython_debug/
158
158
  # and can be added to the global gitignore or merged into this file. For a more nuclear
159
159
  # option (not recommended) you can uncomment the following to ignore the entire idea folder.
160
160
  #.idea/
161
+
162
+ # VSCode
163
+ .vscode/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: nextmv
3
- Version: 0.1.0.dev17
3
+ Version: 0.2.0.dev0
4
4
  Summary: The Python SDK for Nextmv
5
5
  Project-URL: Homepage, https://www.nextmv.io
6
6
  Project-URL: Documentation, https://www.nextmv.io/docs
@@ -215,6 +215,7 @@ Classifier: Operating System :: OS Independent
215
215
  Classifier: Programming Language :: Python :: 3
216
216
  Requires-Python: >=3.10
217
217
  Requires-Dist: pydantic>=2.5.2
218
+ Requires-Dist: pyyaml>=6.0.1
218
219
  Requires-Dist: requests>=2.31.0
219
220
  Description-Content-Type: text/markdown
220
221
 
@@ -229,7 +230,7 @@ Visit the [docs][docs] for more information.
229
230
 
230
231
  ## Installation
231
232
 
232
- Install using `pip`:
233
+ Requires Python `>=3.10`. Install using `pip`:
233
234
 
234
235
  ```bash
235
236
  pip install nextmv
@@ -240,17 +241,17 @@ pip install nextmv
240
241
  Make sure that you have your API key set as an environment variable:
241
242
 
242
243
  ```bash
243
- export NEXTMV_API_KEY=<your-API-key>
244
+ export NEXTMV_API_KEY="<YOUR-API-KEY>"
244
245
  ```
245
246
 
246
- Additionally, you must have a valid app in the Nextmv Cloud.
247
+ Additionally, you must have a valid app in Nextmv Cloud.
247
248
 
248
249
  - Make a run and get the results.
249
250
 
250
251
  ```python
251
252
  import os
252
253
 
253
- from nextmv.cloud import Application, Client
254
+ from nextmv.cloud import Application, Client, PollingOptions
254
255
 
255
256
  input = {
256
257
  "defaults": {"vehicles": {"speed": 20}},
@@ -276,11 +277,12 @@ input = {
276
277
  }
277
278
 
278
279
  client = Client(api_key=os.getenv("NEXTMV_API_KEY"))
279
- app = Application(client=client, id="your-app-id")
280
+ app = Application(client=client, id="<YOUR-APP-ID>")
280
281
  result = app.new_run_with_result(
281
282
  input=input,
282
283
  instance_id="latest",
283
284
  run_options={"solve.duration": "1s"},
285
+ polling_options=PollingOptions(), # Customize the polling options.
284
286
  )
285
287
  print(result.to_dict())
286
288
 
@@ -9,7 +9,7 @@ Visit the [docs][docs] for more information.
9
9
 
10
10
  ## Installation
11
11
 
12
- Install using `pip`:
12
+ Requires Python `>=3.10`. Install using `pip`:
13
13
 
14
14
  ```bash
15
15
  pip install nextmv
@@ -20,17 +20,17 @@ pip install nextmv
20
20
  Make sure that you have your API key set as an environment variable:
21
21
 
22
22
  ```bash
23
- export NEXTMV_API_KEY=<your-API-key>
23
+ export NEXTMV_API_KEY="<YOUR-API-KEY>"
24
24
  ```
25
25
 
26
- Additionally, you must have a valid app in the Nextmv Cloud.
26
+ Additionally, you must have a valid app in Nextmv Cloud.
27
27
 
28
28
  - Make a run and get the results.
29
29
 
30
30
  ```python
31
31
  import os
32
32
 
33
- from nextmv.cloud import Application, Client
33
+ from nextmv.cloud import Application, Client, PollingOptions
34
34
 
35
35
  input = {
36
36
  "defaults": {"vehicles": {"speed": 20}},
@@ -56,11 +56,12 @@ input = {
56
56
  }
57
57
 
58
58
  client = Client(api_key=os.getenv("NEXTMV_API_KEY"))
59
- app = Application(client=client, id="your-app-id")
59
+ app = Application(client=client, id="<YOUR-APP-ID>")
60
60
  result = app.new_run_with_result(
61
61
  input=input,
62
62
  instance_id="latest",
63
63
  run_options={"solve.duration": "1s"},
64
+ polling_options=PollingOptions(), # Customize the polling options.
64
65
  )
65
66
  print(result.to_dict())
66
67
 
@@ -0,0 +1 @@
1
+ __version__ = "v0.2.0.dev0"
@@ -443,7 +443,7 @@ class Application:
443
443
 
444
444
  def new_run(
445
445
  self,
446
- input: dict[str, Any] = None,
446
+ input: dict[str, Any] | BaseModel = None,
447
447
  instance_id: str | None = None,
448
448
  name: str | None = None,
449
449
  description: str | None = None,
@@ -470,6 +470,9 @@ class Application:
470
470
  requests.HTTPError: If the response status code is not 2xx.
471
471
  """
472
472
 
473
+ if isinstance(input, BaseModel):
474
+ input = input.to_dict()
475
+
473
476
  input_size = 0
474
477
  if input is not None:
475
478
  input_size = get_size(input)
@@ -508,7 +511,7 @@ class Application:
508
511
 
509
512
  def new_run_with_result(
510
513
  self,
511
- input: dict[str, Any] = None,
514
+ input: dict[str, Any] | BaseModel = None,
512
515
  instance_id: str | None = None,
513
516
  name: str | None = None,
514
517
  description: str | None = None,
@@ -7,6 +7,7 @@ from typing import Any
7
7
  from urllib.parse import urljoin
8
8
 
9
9
  import requests
10
+ import yaml
10
11
  from requests.adapters import HTTPAdapter, Retry
11
12
 
12
13
  _MAX_LAMBDA_PAYLOAD_SIZE: int = 500 * 1024 * 1024
@@ -16,20 +17,21 @@ _MAX_LAMBDA_PAYLOAD_SIZE: int = 500 * 1024 * 1024
16
17
  @dataclass
17
18
  class Client:
18
19
  """
19
- Client that interacts directly with the Nextmv Cloud API. The API key
20
- must be provided either in the constructor or via the NEXTMV_API_KEY
21
- environment variable.
20
+ Client that interacts directly with the Nextmv Cloud API. The API key will
21
+ be searched, in order of precedence, in: the api_key arg in the
22
+ constructor, the NEXTMV_API_KEY environment variable, the
23
+ ~/.nextmv/config.yaml file used by the Nextmv CLI.
22
24
  """
23
25
 
26
+ api_key: str | None = None
27
+ """API key to use for authenticating with the Nextmv Cloud API. If not
28
+ provided, the client will look for the NEXTMV_API_KEY environment
29
+ variable."""
24
30
  allowed_methods: list[str] = field(
25
31
  default_factory=lambda: ["GET", "POST", "PUT", "DELETE"],
26
32
  )
27
33
  """Allowed HTTP methods to use for retries in requests to the Nextmv Cloud
28
34
  API."""
29
- api_key: str | None = None
30
- """API key to use for authenticating with the Nextmv Cloud API. If not
31
- provided, the client will look for the NEXTMV_API_KEY environment
32
- variable."""
33
35
  backoff_factor: float = 1
34
36
  """Exponential backoff factor to use for requests to the Nextmv Cloud
35
37
  API."""
@@ -38,6 +40,8 @@ class Client:
38
40
  backoff_max: float = 60
39
41
  """Maximum backoff time to use for requests to the Nextmv Cloud API, in
40
42
  seconds."""
43
+ configuration_file: str = "~/.nextmv/config.yaml"
44
+ """Path to the configuration file used by the Nextmv CLI."""
41
45
  headers: dict[str, str] | None = None
42
46
  """Headers to use for requests to the Nextmv Cloud API."""
43
47
  max_retries: int = 10
@@ -55,14 +59,41 @@ class Client:
55
59
  def __post_init__(self):
56
60
  """Logic to run after the class is initialized."""
57
61
 
58
- if self.api_key is None:
59
- api_key = os.getenv("NEXTMV_API_KEY")
60
- if api_key is None:
61
- raise ValueError(
62
- "no API key provided. Either set it in the constructor or "
63
- "set the NEXTMV_API_KEY environment variable."
64
- )
65
- self.api_key = api_key
62
+ if self.api_key is not None and self.api_key != "":
63
+ return
64
+
65
+ if self.api_key == "":
66
+ raise ValueError("api_key cannot be empty")
67
+
68
+ api_key_env = os.getenv("NEXTMV_API_KEY")
69
+ if api_key_env is not None:
70
+ self.api_key = api_key_env
71
+ return
72
+
73
+ config_path = os.path.expanduser(self.configuration_file)
74
+ if not os.path.exists(config_path):
75
+ raise ValueError(
76
+ "no API key set in constructor or NEXTMV_API_KEY env var, and ~/.nextmv/config.yaml does not exist"
77
+ )
78
+
79
+ with open(config_path) as f:
80
+ config = yaml.safe_load(f)
81
+
82
+ profile = os.getenv("NEXTMV_PROFILE")
83
+ parent = config
84
+ if profile is not None:
85
+ parent = config.get(profile)
86
+ if parent is None:
87
+ raise ValueError(f"profile {profile} set via NEXTMV_PROFILE but not found in ~/.nextmv/config.yaml")
88
+
89
+ api_key = parent.get("apikey")
90
+ if api_key is None:
91
+ raise ValueError("no apiKey found in ~/.nextmv/config.yaml")
92
+ self.api_key = api_key
93
+
94
+ endpoint = parent.get("endpoint")
95
+ if endpoint is not None:
96
+ self.url = f"https://{endpoint}"
66
97
 
67
98
  self.headers = {
68
99
  "Authorization": f"Bearer {self.api_key}",
@@ -14,6 +14,7 @@ classifiers = [
14
14
  dependencies = [
15
15
  "pydantic>=2.5.2",
16
16
  "requests>=2.31.0",
17
+ "pyyaml>=6.0.1",
17
18
  ]
18
19
  description = "The Python SDK for Nextmv"
19
20
  dynamic = [
@@ -1,5 +1,6 @@
1
1
  build>=1.0.3
2
2
  pydantic>=2.5.2
3
+ pyyaml>=6.0.1
3
4
  requests>=2.31.0
4
5
  ruff>=0.1.7
5
6
  twine>=4.0.2
@@ -0,0 +1,25 @@
1
+ import os
2
+ import unittest
3
+
4
+ from nextmv.cloud import Client
5
+
6
+
7
+ class TestClient(unittest.TestCase):
8
+ def test_api_key(self):
9
+ client1 = Client(api_key="foo")
10
+ self.assertEqual(client1.api_key, "foo")
11
+
12
+ os.environ["NEXTMV_API_KEY"] = "bar"
13
+ client2 = Client()
14
+ self.assertEqual(client2.api_key, "bar")
15
+ os.environ.pop("NEXTMV_API_KEY")
16
+
17
+ with self.assertRaises(ValueError):
18
+ Client(api_key="")
19
+
20
+ with self.assertRaises(ValueError):
21
+ Client(configuration_file="")
22
+
23
+ os.environ["NEXTMV_PROFILE"] = "i-like-turtles"
24
+ with self.assertRaises(ValueError):
25
+ Client()
File without changes
@@ -1 +0,0 @@
1
- __version__ = "v0.1.0.dev17"
File without changes