nextmv 0.1.0.dev16__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.dev16 → nextmv-0.2.0.dev0}/.github/workflows/publish.yml +68 -38
  2. {nextmv-0.1.0.dev16 → nextmv-0.2.0.dev0}/.gitignore +3 -0
  3. {nextmv-0.1.0.dev16 → nextmv-0.2.0.dev0}/PKG-INFO +8 -6
  4. {nextmv-0.1.0.dev16 → 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.dev16 → nextmv-0.2.0.dev0}/nextmv/cloud/application.py +5 -2
  7. {nextmv-0.1.0.dev16 → nextmv-0.2.0.dev0}/nextmv/cloud/client.py +46 -15
  8. {nextmv-0.1.0.dev16 → nextmv-0.2.0.dev0}/pyproject.toml +1 -0
  9. {nextmv-0.1.0.dev16 → 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.dev16/nextmv/__about__.py +0 -1
  13. {nextmv-0.1.0.dev16 → nextmv-0.2.0.dev0}/.github/workflows/python-lint.yml +0 -0
  14. {nextmv-0.1.0.dev16 → nextmv-0.2.0.dev0}/.github/workflows/python-test.yml +0 -0
  15. {nextmv-0.1.0.dev16 → nextmv-0.2.0.dev0}/LICENSE +0 -0
  16. {nextmv-0.1.0.dev16 → nextmv-0.2.0.dev0}/nextmv/__init__.py +0 -0
  17. {nextmv-0.1.0.dev16 → nextmv-0.2.0.dev0}/nextmv/base_model.py +0 -0
  18. {nextmv-0.1.0.dev16 → nextmv-0.2.0.dev0}/nextmv/cloud/__init__.py +0 -0
  19. {nextmv-0.1.0.dev16 → nextmv-0.2.0.dev0}/nextmv/cloud/acceptance_test.py +0 -0
  20. {nextmv-0.1.0.dev16 → nextmv-0.2.0.dev0}/nextmv/cloud/batch_experiment.py +0 -0
  21. {nextmv-0.1.0.dev16 → nextmv-0.2.0.dev0}/nextmv/cloud/input_set.py +0 -0
  22. {nextmv-0.1.0.dev16 → nextmv-0.2.0.dev0}/nextmv/nextroute/__init__.py +0 -0
  23. {nextmv-0.1.0.dev16 → nextmv-0.2.0.dev0}/nextmv/nextroute/check/__init__.py +0 -0
  24. {nextmv-0.1.0.dev16 → nextmv-0.2.0.dev0}/nextmv/nextroute/check/schema.py +0 -0
  25. {nextmv-0.1.0.dev16 → nextmv-0.2.0.dev0}/nextmv/nextroute/schema/__init__.py +0 -0
  26. {nextmv-0.1.0.dev16 → nextmv-0.2.0.dev0}/nextmv/nextroute/schema/input.py +0 -0
  27. {nextmv-0.1.0.dev16 → nextmv-0.2.0.dev0}/nextmv/nextroute/schema/location.py +0 -0
  28. {nextmv-0.1.0.dev16 → nextmv-0.2.0.dev0}/nextmv/nextroute/schema/output.py +0 -0
  29. {nextmv-0.1.0.dev16 → nextmv-0.2.0.dev0}/nextmv/nextroute/schema/stop.py +0 -0
  30. {nextmv-0.1.0.dev16 → nextmv-0.2.0.dev0}/nextmv/nextroute/schema/vehicle.py +0 -0
  31. {nextmv-0.1.0.dev16 → nextmv-0.2.0.dev0}/nextmv-py.code-workspace +0 -0
  32. {nextmv-0.1.0.dev16 → nextmv-0.2.0.dev0}/tests/__init__.py +0 -0
  33. {nextmv-0.1.0.dev16/tests/nextroute → nextmv-0.2.0.dev0/tests/cloud}/__init__.py +0 -0
  34. {nextmv-0.1.0.dev16/tests/nextroute/schema → nextmv-0.2.0.dev0/tests/nextroute}/__init__.py +0 -0
  35. {nextmv-0.1.0.dev16 → nextmv-0.2.0.dev0}/tests/nextroute/schema/input.json +0 -0
  36. {nextmv-0.1.0.dev16 → nextmv-0.2.0.dev0}/tests/nextroute/schema/output.json +0 -0
  37. {nextmv-0.1.0.dev16 → nextmv-0.2.0.dev0}/tests/nextroute/schema/output_with_check.json +0 -0
  38. {nextmv-0.1.0.dev16 → nextmv-0.2.0.dev0}/tests/nextroute/schema/test_input.py +0 -0
  39. {nextmv-0.1.0.dev16 → nextmv-0.2.0.dev0}/tests/nextroute/schema/test_output.py +0 -0
  40. {nextmv-0.1.0.dev16 → nextmv-0.2.0.dev0}/tests/test_base_model.py +0 -0
@@ -7,27 +7,22 @@ on:
7
7
  description: "The version to release"
8
8
  required: true
9
9
  IS_PRE_RELEASE:
10
- description: "Whether this is a pre-release"
10
+ description: "It IS a pre-release"
11
11
  required: true
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 }}
21
+ SSH_AUTH_SOCK: /tmp/ssh_agent.sock
22
+ permissions:
23
+ contents: write
24
24
  steps:
25
- - name: git clone develop
26
- uses: actions/checkout@v4
27
- with:
28
- ref: develop
29
-
30
- - name: Set up Python
25
+ - name: set up Python
31
26
  uses: actions/setup-python@v4
32
27
  with:
33
28
  python-version: "3.12"
@@ -35,33 +30,9 @@ jobs:
35
30
  - name: install dependencies
36
31
  run: |
37
32
  pip install --upgrade pip
38
- pip install build twine hatch
39
-
40
- - name: upgrade version with hatch
41
- run: hatch version ${{ env.VERSION }}
42
-
43
- - name: build binary wheel and source tarball
44
- env:
45
- TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
46
- TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
47
- run: |
48
- python -m build
49
-
50
- - name: publish to TestPyPI
51
- env:
52
- TWINE_USERNAME: ${{ secrets.TESTPYPI_USERNAME }}
53
- TWINE_PASSWORD: ${{ secrets.TESTPYPI_PASSWORD }}
54
- run: twine upload --repository testpypi dist/*
55
-
56
- - name: publish to PyPI
57
- env:
58
- TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
59
- TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
60
- run: twine upload dist/*
33
+ pip install build hatch
61
34
 
62
35
  - name: configure git with the bot credentials
63
- env:
64
- SSH_AUTH_SOCK: /tmp/ssh_agent.sock
65
36
  run: |
66
37
  mkdir -p ~/.ssh
67
38
  ssh-keyscan github.com >> ~/.ssh/known_hosts
@@ -73,10 +44,15 @@ jobs:
73
44
 
74
45
  git config --global user.name "nextmv-bot"
75
46
  git config --global user.email "tech+gh-nextmv-bot@nextmv.io"
76
- git config --global url."git@github.com:".insteadOf "https://github.com/"
77
47
  git config --global gpg.format ssh
78
48
  git config --global user.signingkey ~/.ssh/signing.key
79
49
 
50
+ git clone git@github.com:nextmv-io/nextmv-py.git
51
+
52
+ - name: upgrade version with hatch
53
+ run: hatch version ${{ env.VERSION }}
54
+ working-directory: ./nextmv-py
55
+
80
56
  - name: commit new version
81
57
  run: |
82
58
  git add nextmv/__about__.py
@@ -84,6 +60,7 @@ jobs:
84
60
  git push
85
61
  git tag $VERSION
86
62
  git push origin $VERSION
63
+ working-directory: ./nextmv-py
87
64
 
88
65
  - name: create release
89
66
  run: |
@@ -92,7 +69,60 @@ jobs:
92
69
  PRERELEASE_FLAG="--prerelease"
93
70
  fi
94
71
 
95
- gh release create -R nextmv-io/nextmv-py $VERSION \
72
+ gh release create $VERSION \
96
73
  --verify-tag \
97
74
  --generate-notes \
98
75
  --title $VERSION $PRERELEASE_FLAG
76
+ working-directory: ./nextmv-py
77
+
78
+ - name: ensure passing build
79
+ run: python -m build
80
+ working-directory: ./nextmv-py
81
+
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
103
+
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.dev16
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.dev16"
File without changes