myvsb-developer-api 1.0.4__tar.gz → 1.0.6__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 (38) hide show
  1. myvsb_developer_api-1.0.6/PKG-INFO +63 -0
  2. myvsb_developer_api-1.0.6/README.md +36 -0
  3. {myvsb_developer_api-1.0.4 → myvsb_developer_api-1.0.6}/pyproject.toml +10 -1
  4. myvsb_developer_api-1.0.6/src/myskoda_openapi/api_layer/const.py +25 -0
  5. {myvsb_developer_api-1.0.4 → myvsb_developer_api-1.0.6}/src/myskoda_openapi/api_layer/open_api_client.py +3 -2
  6. {myvsb_developer_api-1.0.4 → myvsb_developer_api-1.0.6}/src/myskoda_openapi/api_layer/rest_api.py +2 -2
  7. myvsb_developer_api-1.0.6/src/myskoda_openapi/cli/__init__.py +125 -0
  8. myvsb_developer_api-1.0.6/src/myskoda_openapi/cli/__main__.py +6 -0
  9. myvsb_developer_api-1.0.6/src/myskoda_openapi/cli/operations.py +165 -0
  10. myvsb_developer_api-1.0.6/src/myskoda_openapi/cli/requests.py +107 -0
  11. myvsb_developer_api-1.0.6/src/myskoda_openapi/cli/utils.py +110 -0
  12. {myvsb_developer_api-1.0.4 → myvsb_developer_api-1.0.6}/src/myskoda_openapi/models/charging_profiles.py +1 -1
  13. {myvsb_developer_api-1.0.4 → myvsb_developer_api-1.0.6}/src/myskoda_openapi/models/configurations.py +1 -10
  14. {myvsb_developer_api-1.0.4 → myvsb_developer_api-1.0.6}/src/myskoda_openapi/models/enums.py +3 -0
  15. myvsb_developer_api-1.0.6/src/myvsb_developer_api.egg-info/PKG-INFO +63 -0
  16. {myvsb_developer_api-1.0.4 → myvsb_developer_api-1.0.6}/src/myvsb_developer_api.egg-info/SOURCES.txt +6 -0
  17. myvsb_developer_api-1.0.6/src/myvsb_developer_api.egg-info/entry_points.txt +2 -0
  18. {myvsb_developer_api-1.0.4 → myvsb_developer_api-1.0.6}/src/myvsb_developer_api.egg-info/requires.txt +6 -0
  19. myvsb_developer_api-1.0.4/PKG-INFO +0 -24
  20. myvsb_developer_api-1.0.4/README.md +0 -2
  21. myvsb_developer_api-1.0.4/src/myskoda_openapi/api_layer/const.py +0 -4
  22. myvsb_developer_api-1.0.4/src/myvsb_developer_api.egg-info/PKG-INFO +0 -24
  23. {myvsb_developer_api-1.0.4 → myvsb_developer_api-1.0.6}/LICENSE +0 -0
  24. {myvsb_developer_api-1.0.4 → myvsb_developer_api-1.0.6}/setup.cfg +0 -0
  25. {myvsb_developer_api-1.0.4 → myvsb_developer_api-1.0.6}/src/myskoda_openapi/api_layer/exceptions.py +0 -0
  26. {myvsb_developer_api-1.0.4 → myvsb_developer_api-1.0.6}/src/myskoda_openapi/api_layer/utils.py +0 -0
  27. {myvsb_developer_api-1.0.4 → myvsb_developer_api-1.0.6}/src/myskoda_openapi/models/active_ventilation.py +0 -0
  28. {myvsb_developer_api-1.0.4 → myvsb_developer_api-1.0.6}/src/myskoda_openapi/models/air_conditioning.py +0 -0
  29. {myvsb_developer_api-1.0.4 → myvsb_developer_api-1.0.6}/src/myskoda_openapi/models/auxiliary_heating.py +0 -0
  30. {myvsb_developer_api-1.0.4 → myvsb_developer_api-1.0.6}/src/myskoda_openapi/models/base_model.py +0 -0
  31. {myvsb_developer_api-1.0.4 → myvsb_developer_api-1.0.6}/src/myskoda_openapi/models/charging.py +0 -0
  32. {myvsb_developer_api-1.0.4 → myvsb_developer_api-1.0.6}/src/myskoda_openapi/models/common.py +0 -0
  33. {myvsb_developer_api-1.0.4 → myvsb_developer_api-1.0.6}/src/myskoda_openapi/models/driving_range.py +0 -0
  34. {myvsb_developer_api-1.0.4 → myvsb_developer_api-1.0.6}/src/myskoda_openapi/models/parking_position.py +0 -0
  35. {myvsb_developer_api-1.0.4 → myvsb_developer_api-1.0.6}/src/myskoda_openapi/models/vehicle.py +0 -0
  36. {myvsb_developer_api-1.0.4 → myvsb_developer_api-1.0.6}/src/myskoda_openapi/models/vehicle_status.py +0 -0
  37. {myvsb_developer_api-1.0.4 → myvsb_developer_api-1.0.6}/src/myvsb_developer_api.egg-info/dependency_links.txt +0 -0
  38. {myvsb_developer_api-1.0.4 → myvsb_developer_api-1.0.6}/src/myvsb_developer_api.egg-info/top_level.txt +0 -0
@@ -0,0 +1,63 @@
1
+ Metadata-Version: 2.4
2
+ Name: myvsb-developer-api
3
+ Version: 1.0.6
4
+ Summary: Asynchronous Python library for the MyVSB Developer API
5
+ Author-email: net0045 <stepan.netolicky@vsb.cz>
6
+ Project-URL: Homepage, https://github.com/mobility-lab-vsb/myvsb-developer-api
7
+ Project-URL: Bug Tracker, https://github.com/mobility-lab-vsb/myvsb-developer-api/issues
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Framework :: AsyncIO
12
+ Requires-Python: >=3.11
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: aiohttp>=3.9.0
16
+ Requires-Dist: pydantic>=2.5.0
17
+ Provides-Extra: test
18
+ Requires-Dist: pytest>=9.0.0; extra == "test"
19
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == "test"
20
+ Requires-Dist: aresponses>=3.0.0; extra == "test"
21
+ Provides-Extra: cli
22
+ Requires-Dist: asyncclick~=8.1; extra == "cli"
23
+ Requires-Dist: coloredlogs~=15.0; extra == "cli"
24
+ Requires-Dist: pygments~=2.18; extra == "cli"
25
+ Requires-Dist: pyyaml~=6.0; extra == "cli"
26
+ Dynamic: license-file
27
+
28
+ # vsb-developer-api
29
+
30
+ VSB developer API is a Python library to interact with the new VSB API for connected-vehicle data and remote control of VSB vehicles.
31
+
32
+ ## As CLI
33
+
34
+ The library ships with a command line interface called `skodactl`, which is useful for manually
35
+ testing the API endpoints. Install it together with the CLI dependencies:
36
+
37
+ ```sh
38
+ pip install "myvsb-developer-api[cli]"
39
+ ```
40
+
41
+ You authenticate with your API key, either via the `--api-key` option or the `SKODA_API_KEY`
42
+ environment variable, and address a vehicle by its VIN:
43
+
44
+ ```sh
45
+ skodactl --api-key XXX get-vehicle DMBGF9NY3NF032963
46
+ ```
47
+
48
+ By default, the CLI talks to the production endpoint. Point it at the public test endpoint with
49
+ `--endpoint test`, switch to YAML output with `--format yaml`, or log HTTP requests and responses
50
+ with `--trace`:
51
+
52
+ ```sh
53
+ skodactl --api-key XXX --endpoint test get-vehicle-status DMBGF9NY3NF032963
54
+ SKODA_API_KEY=XXX skodactl --format yaml --trace get-charging DMBGF9NY3NF032963
55
+ ```
56
+
57
+ Remote operations can be started as well:
58
+
59
+ ```sh
60
+ skodactl --api-key XXX start-air-conditioning DMBGF9NY3NF032963 --temperature 21
61
+ ```
62
+
63
+ Run `skodactl --help` for the full list of commands and options.
@@ -0,0 +1,36 @@
1
+ # vsb-developer-api
2
+
3
+ VSB developer API is a Python library to interact with the new VSB API for connected-vehicle data and remote control of VSB vehicles.
4
+
5
+ ## As CLI
6
+
7
+ The library ships with a command line interface called `skodactl`, which is useful for manually
8
+ testing the API endpoints. Install it together with the CLI dependencies:
9
+
10
+ ```sh
11
+ pip install "myvsb-developer-api[cli]"
12
+ ```
13
+
14
+ You authenticate with your API key, either via the `--api-key` option or the `SKODA_API_KEY`
15
+ environment variable, and address a vehicle by its VIN:
16
+
17
+ ```sh
18
+ skodactl --api-key XXX get-vehicle DMBGF9NY3NF032963
19
+ ```
20
+
21
+ By default, the CLI talks to the production endpoint. Point it at the public test endpoint with
22
+ `--endpoint test`, switch to YAML output with `--format yaml`, or log HTTP requests and responses
23
+ with `--trace`:
24
+
25
+ ```sh
26
+ skodactl --api-key XXX --endpoint test get-vehicle-status DMBGF9NY3NF032963
27
+ SKODA_API_KEY=XXX skodactl --format yaml --trace get-charging DMBGF9NY3NF032963
28
+ ```
29
+
30
+ Remote operations can be started as well:
31
+
32
+ ```sh
33
+ skodactl --api-key XXX start-air-conditioning DMBGF9NY3NF032963 --temperature 21
34
+ ```
35
+
36
+ Run `skodactl --help` for the full list of commands and options.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "myvsb-developer-api"
7
- version = "1.0.4"
7
+ version = "1.0.6"
8
8
  authors = [
9
9
  { name="net0045", email="stepan.netolicky@vsb.cz" },
10
10
  ]
@@ -26,6 +26,9 @@ dependencies = [
26
26
  "Homepage" = "https://github.com/mobility-lab-vsb/myvsb-developer-api"
27
27
  "Bug Tracker" = "https://github.com/mobility-lab-vsb/myvsb-developer-api/issues"
28
28
 
29
+ [project.scripts]
30
+ skodactl = "myskoda_openapi.cli:cli"
31
+
29
32
  [tool.pytest.ini_options]
30
33
  pythonpath = ["src/myskoda_openapi"]
31
34
 
@@ -35,3 +38,9 @@ test = [
35
38
  "pytest-asyncio>=0.23.0",
36
39
  "aresponses>=3.0.0",
37
40
  ]
41
+ cli = [
42
+ "asyncclick ~=8.1",
43
+ "coloredlogs ~=15.0",
44
+ "pygments ~=2.18",
45
+ "pyyaml ~=6.0",
46
+ ]
@@ -0,0 +1,25 @@
1
+ from enum import StrEnum
2
+
3
+ SANDBOX_BASE_URL = "https://developer.sandbox.connect.skoda-auto.cz"
4
+ PRODUCTION_BASE_URL = "https://developer.connect.skoda-auto.cz"
5
+ TEST_BASE_URL = "https://public.test-api.connect.skoda-auto.cz"
6
+ BETA_URL = "https://public.api.connect.skoda-auto.cz"
7
+
8
+
9
+ class Endpoints(StrEnum):
10
+ PRODUCTION = "production"
11
+ SANDBOX = "sandbox"
12
+ TEST = "test"
13
+ BETA = "beta"
14
+
15
+ @property
16
+ def url(self) -> str:
17
+ return ENDPOINT_URLS[self]
18
+
19
+
20
+ ENDPOINT_URLS = {
21
+ Endpoints.PRODUCTION: PRODUCTION_BASE_URL,
22
+ Endpoints.SANDBOX: SANDBOX_BASE_URL,
23
+ Endpoints.TEST: TEST_BASE_URL,
24
+ Endpoints.BETA: BETA_URL,
25
+ }
@@ -1,6 +1,7 @@
1
1
  import logging
2
2
  from typing import Any, Optional, List
3
3
  from aiohttp import ClientSession
4
+ from .const import BETA_URL
4
5
  from .rest_api import PostEndpointResult, SkodaRestAPI
5
6
  from ..models.vehicle import VehicleResponse, Odometer
6
7
  from ..models.vehicle_status import VehicleStatus
@@ -16,8 +17,8 @@ from ..models.configurations import StartAirConditioningConfiguration, StartAuxi
16
17
  class OpenAPIClient:
17
18
  """Client for interacting with rest API and used in HomeAssistant."""
18
19
 
19
- def __init__(self, api_key: str, session: ClientSession) -> None:
20
- self.rest_api = SkodaRestAPI(api_key=api_key, session=session)
20
+ def __init__(self, api_key: str, session: ClientSession, base_url: str = BETA_URL) -> None:
21
+ self.rest_api = SkodaRestAPI(api_key=api_key, session=session, base_url=base_url)
21
22
 
22
23
  async def get_vehicle(self, vin: str, include: Optional[List[str]] = None) -> VehicleResponse:
23
24
  """Get the actual vehicle data and return the parsed Pydantic model."""
@@ -46,10 +46,10 @@ class PostEndpointResult:
46
46
  class SkodaRestAPI:
47
47
  """Rest API client for interacting with the Škoda Open API."""
48
48
 
49
- def __init__(self, api_key: str, session: ClientSession) -> None:
49
+ def __init__(self, api_key: str, session: ClientSession, base_url: str = BETA_URL) -> None:
50
50
  self._api_key = api_key
51
51
  self._session = session
52
- self._base_url = BETA_URL # Default to BETA_URL; can be changed to SANDBOX_BASE_URL or PRODUCTION_BASE_URL as needed.
52
+ self._base_url = base_url # Default to BETA_URL; can be changed to SANDBOX_BASE_URL or PRODUCTION_BASE_URL as needed.
53
53
  #TODO will be exteded with methods for authorization, token management, and other endpoints as needed.
54
54
 
55
55
  async def _make_post_request(self, url: str, json_data: Optional[dict] = None) -> int:
@@ -0,0 +1,125 @@
1
+ """CLI to test all API functions and models.
2
+
3
+ Execute with:
4
+ skodactl
5
+ """
6
+
7
+ from logging import DEBUG, INFO
8
+
9
+ import asyncclick as click
10
+ import coloredlogs
11
+ from aiohttp import ClientSession
12
+ from asyncclick.core import Context
13
+
14
+ from ..api_layer.const import ENDPOINT_URLS, Endpoints
15
+ from ..api_layer.open_api_client import OpenAPIClient
16
+ from .operations import (
17
+ start_active_ventilation,
18
+ start_air_conditioning,
19
+ start_auxiliary_heating,
20
+ start_charging,
21
+ stop_active_ventilation,
22
+ stop_air_conditioning,
23
+ stop_auxiliary_heating,
24
+ stop_charging,
25
+ )
26
+ from .requests import (
27
+ get_active_ventilation,
28
+ get_air_conditioning,
29
+ get_auxiliary_heating,
30
+ get_charging,
31
+ get_charging_profiles,
32
+ get_fuel_status,
33
+ get_odometer,
34
+ get_parking_position,
35
+ get_vehicle,
36
+ get_vehicle_status,
37
+ )
38
+ from .utils import TRACE_CONFIG, Format, StrEnumChoice, print_json, print_yaml
39
+
40
+
41
+ @click.group()
42
+ @click.option(
43
+ "api_key",
44
+ "--api-key",
45
+ help="API key used to authenticate against the Skoda API.",
46
+ required=True,
47
+ envvar="SKODA_API_KEY",
48
+ )
49
+ @click.option(
50
+ "endpoint",
51
+ "--endpoint",
52
+ help="Select the API endpoint to use.",
53
+ type=StrEnumChoice(Endpoints),
54
+ default=Endpoints.BETA,
55
+ )
56
+ @click.option(
57
+ "output_format",
58
+ "--format",
59
+ help="Select the output format. JSON or YAML.",
60
+ type=StrEnumChoice(Format),
61
+ default=Format.JSON,
62
+ )
63
+ @click.option("verbose", "--verbose", help="Enable verbose logging.", is_flag=True)
64
+ @click.option("trace", "--trace", help="Enable tracing of HTTP requests.", is_flag=True)
65
+ @click.pass_context
66
+ async def cli(
67
+ ctx: Context,
68
+ api_key: str,
69
+ endpoint: Endpoints,
70
+ output_format: Format,
71
+ verbose: bool,
72
+ trace: bool,
73
+ ) -> None:
74
+ """Interact with the Skoda Open API."""
75
+ coloredlogs.install(level=DEBUG if verbose or trace else INFO)
76
+ ctx.ensure_object(dict)
77
+ ctx.obj["api_key"] = api_key
78
+ ctx.obj["endpoint"] = endpoint
79
+ if output_format == Format.JSON:
80
+ ctx.obj["print"] = print_json
81
+ elif output_format == Format.YAML:
82
+ ctx.obj["print"] = print_yaml
83
+
84
+ trace_configs = [TRACE_CONFIG] if trace else []
85
+
86
+ if ctx.resilient_parsing:
87
+ return
88
+
89
+ session = ClientSession(trace_configs=trace_configs)
90
+ client = OpenAPIClient(
91
+ api_key=api_key,
92
+ session=session,
93
+ base_url=ENDPOINT_URLS[endpoint],
94
+ )
95
+ ctx.obj["client"] = client
96
+ ctx.obj["session"] = session
97
+
98
+ async def _close() -> None:
99
+ await session.close()
100
+
101
+ ctx.call_on_close(_close)
102
+
103
+
104
+ cli.add_command(get_active_ventilation)
105
+ cli.add_command(get_air_conditioning)
106
+ cli.add_command(get_auxiliary_heating)
107
+ cli.add_command(get_charging)
108
+ cli.add_command(get_charging_profiles)
109
+ cli.add_command(get_fuel_status)
110
+ cli.add_command(get_odometer)
111
+ cli.add_command(get_parking_position)
112
+ cli.add_command(get_vehicle)
113
+ cli.add_command(get_vehicle_status)
114
+ cli.add_command(start_active_ventilation)
115
+ cli.add_command(start_air_conditioning)
116
+ cli.add_command(start_auxiliary_heating)
117
+ cli.add_command(start_charging)
118
+ cli.add_command(stop_active_ventilation)
119
+ cli.add_command(stop_air_conditioning)
120
+ cli.add_command(stop_auxiliary_heating)
121
+ cli.add_command(stop_charging)
122
+
123
+
124
+ if __name__ == "__main__":
125
+ cli()
@@ -0,0 +1,6 @@
1
+ """Allow execution with ``python -m myskoda_openapi.cli``."""
2
+
3
+ from . import cli
4
+
5
+ if __name__ == "__main__":
6
+ cli()
@@ -0,0 +1,165 @@
1
+ """Commands for the CLI for operations that can be performed."""
2
+
3
+ from typing import TYPE_CHECKING, Optional
4
+
5
+ import asyncclick as click
6
+ from asyncclick.core import Context
7
+
8
+ from ..models.enums import TemperatureUnit
9
+ from .utils import StrEnumChoice, handle_request
10
+
11
+ if TYPE_CHECKING:
12
+ from ..api_layer.open_api_client import OpenAPIClient
13
+
14
+ TEMPERATURE_UNITS = [TemperatureUnit.CELSIUS, TemperatureUnit.FAHRENHEIT]
15
+
16
+
17
+ @click.command()
18
+ @click.argument("vin")
19
+ @click.option(
20
+ "temperature",
21
+ "--temperature",
22
+ type=float,
23
+ required=True,
24
+ help="Target temperature for the air conditioning.",
25
+ )
26
+ @click.option(
27
+ "unit",
28
+ "--unit",
29
+ type=StrEnumChoice(TEMPERATURE_UNITS),
30
+ default=TemperatureUnit.CELSIUS,
31
+ help="Temperature unit, CELSIUS or FAHRENHEIT.",
32
+ )
33
+ @click.option(
34
+ "with_external_power",
35
+ "--with-external-power",
36
+ is_flag=True,
37
+ help="Start the air conditioning with external power.",
38
+ )
39
+ @click.pass_context
40
+ async def start_air_conditioning(
41
+ ctx: Context,
42
+ vin: str,
43
+ temperature: float,
44
+ unit: TemperatureUnit,
45
+ with_external_power: bool,
46
+ ) -> None:
47
+ """Start the air conditioning with the provided target temperature."""
48
+ client: OpenAPIClient = ctx.obj["client"]
49
+ await handle_request(
50
+ ctx,
51
+ client.start_air_conditioning,
52
+ vin,
53
+ temperature,
54
+ unit.value,
55
+ not with_external_power,
56
+ )
57
+
58
+
59
+ @click.command()
60
+ @click.argument("vin")
61
+ @click.pass_context
62
+ async def stop_air_conditioning(ctx: Context, vin: str) -> None:
63
+ """Stop the air conditioning for the specified VIN."""
64
+ client: OpenAPIClient = ctx.obj["client"]
65
+ await handle_request(ctx, client.stop_air_conditioning, vin)
66
+
67
+
68
+ @click.command()
69
+ @click.argument("vin")
70
+ @click.option("spin", "--spin", required=True, help="SPIN of the vehicle.")
71
+ @click.option(
72
+ "duration",
73
+ "--duration",
74
+ type=int,
75
+ default=120,
76
+ help="Duration of heating in seconds.",
77
+ )
78
+ @click.option(
79
+ "start_mode",
80
+ "--start-mode",
81
+ default="HEATING",
82
+ help="Start mode, HEATING or VENTILATION.",
83
+ )
84
+ @click.option(
85
+ "temperature",
86
+ "--temperature",
87
+ type=float,
88
+ required=False,
89
+ default=None,
90
+ help="Optional target temperature.",
91
+ )
92
+ @click.option(
93
+ "unit",
94
+ "--unit",
95
+ type=StrEnumChoice(TEMPERATURE_UNITS),
96
+ default=TemperatureUnit.CELSIUS,
97
+ help="Temperature unit, CELSIUS or FAHRENHEIT.",
98
+ )
99
+ @click.pass_context
100
+ async def start_auxiliary_heating(
101
+ ctx: Context,
102
+ vin: str,
103
+ spin: str,
104
+ duration: int,
105
+ start_mode: str,
106
+ temperature: Optional[float],
107
+ unit: TemperatureUnit,
108
+ ) -> None:
109
+ """Start the auxiliary heating for the specified VIN."""
110
+ client: OpenAPIClient = ctx.obj["client"]
111
+ await handle_request(
112
+ ctx,
113
+ client.start_auxiliary_heating,
114
+ vin,
115
+ spin,
116
+ duration,
117
+ start_mode,
118
+ temperature,
119
+ unit.value,
120
+ )
121
+
122
+
123
+ @click.command()
124
+ @click.argument("vin")
125
+ @click.pass_context
126
+ async def stop_auxiliary_heating(ctx: Context, vin: str) -> None:
127
+ """Stop the auxiliary heating for the specified VIN."""
128
+ client: OpenAPIClient = ctx.obj["client"]
129
+ await handle_request(ctx, client.stop_auxiliary_heating, vin)
130
+
131
+
132
+ @click.command()
133
+ @click.argument("vin")
134
+ @click.pass_context
135
+ async def start_active_ventilation(ctx: Context, vin: str) -> None:
136
+ """Start the active ventilation for the specified VIN."""
137
+ client: OpenAPIClient = ctx.obj["client"]
138
+ await handle_request(ctx, client.start_active_ventilation, vin)
139
+
140
+
141
+ @click.command()
142
+ @click.argument("vin")
143
+ @click.pass_context
144
+ async def stop_active_ventilation(ctx: Context, vin: str) -> None:
145
+ """Stop the active ventilation for the specified VIN."""
146
+ client: OpenAPIClient = ctx.obj["client"]
147
+ await handle_request(ctx, client.stop_active_ventilation, vin)
148
+
149
+
150
+ @click.command()
151
+ @click.argument("vin")
152
+ @click.pass_context
153
+ async def start_charging(ctx: Context, vin: str) -> None:
154
+ """Start charging for the specified VIN."""
155
+ client: OpenAPIClient = ctx.obj["client"]
156
+ await handle_request(ctx, client.start_charging, vin)
157
+
158
+
159
+ @click.command()
160
+ @click.argument("vin")
161
+ @click.pass_context
162
+ async def stop_charging(ctx: Context, vin: str) -> None:
163
+ """Stop charging for the specified VIN."""
164
+ client: OpenAPIClient = ctx.obj["client"]
165
+ await handle_request(ctx, client.stop_charging, vin)
@@ -0,0 +1,107 @@
1
+ """Commands dealing with reading data from the Rest API."""
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ import asyncclick as click
6
+ from asyncclick.core import Context
7
+
8
+ from .utils import handle_request
9
+
10
+ if TYPE_CHECKING:
11
+ from ..api_layer.open_api_client import OpenAPIClient
12
+
13
+
14
+ @click.command()
15
+ @click.argument("vin")
16
+ @click.option(
17
+ "include",
18
+ "--include",
19
+ help="Optional field to include in the response. Can be specified multiple times.",
20
+ multiple=True,
21
+ )
22
+ @click.pass_context
23
+ async def get_vehicle(ctx: Context, vin: str, include: tuple[str, ...]) -> None:
24
+ """Print info for the specified VIN."""
25
+ client: OpenAPIClient = ctx.obj["client"]
26
+ await handle_request(ctx, client.get_vehicle, vin, list(include) or None)
27
+
28
+
29
+ @click.command()
30
+ @click.argument("vin")
31
+ @click.pass_context
32
+ async def get_vehicle_status(ctx: Context, vin: str) -> None:
33
+ """Print the current status for the specified VIN."""
34
+ client: OpenAPIClient = ctx.obj["client"]
35
+ await handle_request(ctx, client.get_vehicle_status, vin)
36
+
37
+
38
+ @click.command()
39
+ @click.argument("vin")
40
+ @click.pass_context
41
+ async def get_air_conditioning(ctx: Context, vin: str) -> None:
42
+ """Print the current air conditioning status for the specified VIN."""
43
+ client: OpenAPIClient = ctx.obj["client"]
44
+ await handle_request(ctx, client.get_air_conditioning, vin)
45
+
46
+
47
+ @click.command()
48
+ @click.argument("vin")
49
+ @click.pass_context
50
+ async def get_parking_position(ctx: Context, vin: str) -> None:
51
+ """Print the current parking position for the specified VIN."""
52
+ client: OpenAPIClient = ctx.obj["client"]
53
+ await handle_request(ctx, client.get_parking_positions, vin)
54
+
55
+
56
+ @click.command()
57
+ @click.argument("vin")
58
+ @click.pass_context
59
+ async def get_auxiliary_heating(ctx: Context, vin: str) -> None:
60
+ """Print the current auxiliary heating status for the specified VIN."""
61
+ client: OpenAPIClient = ctx.obj["client"]
62
+ await handle_request(ctx, client.get_auxiliary_heating, vin)
63
+
64
+
65
+ @click.command()
66
+ @click.argument("vin")
67
+ @click.pass_context
68
+ async def get_odometer(ctx: Context, vin: str) -> None:
69
+ """Print the current odometer for the specified VIN."""
70
+ client: OpenAPIClient = ctx.obj["client"]
71
+ await handle_request(ctx, client.get_odometer, vin)
72
+
73
+
74
+ @click.command()
75
+ @click.argument("vin")
76
+ @click.pass_context
77
+ async def get_charging(ctx: Context, vin: str) -> None:
78
+ """Print the current charging status for the specified VIN."""
79
+ client: OpenAPIClient = ctx.obj["client"]
80
+ await handle_request(ctx, client.get_charging, vin)
81
+
82
+
83
+ @click.command()
84
+ @click.argument("vin")
85
+ @click.pass_context
86
+ async def get_active_ventilation(ctx: Context, vin: str) -> None:
87
+ """Print the current active ventilation status for the specified VIN."""
88
+ client: OpenAPIClient = ctx.obj["client"]
89
+ await handle_request(ctx, client.get_active_ventilation, vin)
90
+
91
+
92
+ @click.command()
93
+ @click.argument("vin")
94
+ @click.pass_context
95
+ async def get_charging_profiles(ctx: Context, vin: str) -> None:
96
+ """Print the charging profiles for the specified VIN."""
97
+ client: OpenAPIClient = ctx.obj["client"]
98
+ await handle_request(ctx, client.get_charging_profiles, vin)
99
+
100
+
101
+ @click.command()
102
+ @click.argument("vin")
103
+ @click.pass_context
104
+ async def get_fuel_status(ctx: Context, vin: str) -> None:
105
+ """Print the fuel status for the specified VIN."""
106
+ client: OpenAPIClient = ctx.obj["client"]
107
+ await handle_request(ctx, client.get_fuel_status, vin)
@@ -0,0 +1,110 @@
1
+ """Utilities for the command line interface."""
2
+
3
+ import json
4
+ import logging
5
+ import sys
6
+ from collections.abc import Awaitable, Callable
7
+ from enum import Enum, StrEnum
8
+ from types import SimpleNamespace
9
+ from typing import Any
10
+
11
+ import asyncclick as click
12
+ import yaml
13
+ from aiohttp import ClientSession, TraceConfig
14
+ from aiohttp.tracing import TraceRequestEndParams
15
+ from asyncclick.core import Context
16
+ from pydantic import BaseModel, ValidationError
17
+ from pygments import highlight
18
+ from pygments.formatters import TerminalFormatter
19
+ from pygments.lexer import Lexer
20
+ from pygments.lexers import JsonLexer, YamlLexer
21
+
22
+ from ..api_layer.exceptions import OpenApiError
23
+ from ..api_layer.rest_api import PostEndpointResult
24
+
25
+ _LOGGER = logging.getLogger(__name__)
26
+
27
+
28
+ class Format(StrEnum):
29
+ JSON = "json"
30
+ YAML = "yaml"
31
+
32
+
33
+ class StrEnumChoice(click.Choice):
34
+ """Choice that maps enum values (not names) from the command line to enum members."""
35
+
36
+ def normalize_choice(self, choice: object, ctx: Context | None) -> str:
37
+ if isinstance(choice, Enum):
38
+ return str(choice.value)
39
+ return super().normalize_choice(choice, ctx)
40
+
41
+
42
+ async def handle_request(
43
+ ctx: Context,
44
+ func: Callable[..., Awaitable],
45
+ *args: Any,
46
+ ) -> None:
47
+ """Handle API requests and perform error handling."""
48
+ try:
49
+ result = await func(*args)
50
+ if isinstance(result, BaseModel):
51
+ ctx.obj["print"](result.model_dump(mode="json", by_alias=True))
52
+ elif isinstance(result, PostEndpointResult):
53
+ ctx.obj["print"](
54
+ {
55
+ "url": result.url,
56
+ "status": result.status,
57
+ "headers": result.headers,
58
+ }
59
+ )
60
+ elif result is None:
61
+ ctx.obj["print"]({"status": "ok"})
62
+ else:
63
+ ctx.obj["print"](result)
64
+ except (OpenApiError, ValidationError) as err:
65
+ status_code = err.status_code if isinstance(err, OpenApiError) else None
66
+ ctx.obj["print"](
67
+ {
68
+ "error": type(err).__name__,
69
+ "message": str(err),
70
+ "status_code": status_code,
71
+ }
72
+ )
73
+
74
+
75
+ def highlight_for_console(text: str, lexer: Lexer) -> str:
76
+ if sys.stdout.isatty():
77
+ return highlight(text, lexer, TerminalFormatter())
78
+ return text
79
+
80
+
81
+ def print_json(data: Any) -> None:
82
+ print(highlight_for_console(json.dumps(data, indent=4, ensure_ascii=False), JsonLexer()))
83
+
84
+
85
+ def print_yaml(data: Any) -> None:
86
+ print(highlight_for_console(yaml.dump(data, allow_unicode=True), YamlLexer()))
87
+
88
+
89
+ async def trace_response(
90
+ _session: ClientSession,
91
+ _trace_config_ctx: SimpleNamespace,
92
+ params: TraceRequestEndParams,
93
+ ) -> None:
94
+ """Log response details. Used in aiohttp.TraceConfig."""
95
+ try:
96
+ resp_text = await params.response.text()
97
+ except UnicodeDecodeError:
98
+ resp_text = "<non-UTF-8 response body>"
99
+ _LOGGER.debug(
100
+ "Trace: %s %s - response: %s (%s bytes) %s",
101
+ params.method,
102
+ str(params.url)[:60],
103
+ params.response.status,
104
+ params.response.content_length,
105
+ resp_text[:5000],
106
+ )
107
+
108
+
109
+ TRACE_CONFIG = TraceConfig()
110
+ TRACE_CONFIG.on_request_end.append(trace_response)
@@ -35,7 +35,7 @@ class ChargingProfile(BaseModel):
35
35
  id: int
36
36
  name: str
37
37
  settings: ChargingProfileSettings
38
- preferredChargingTimes: List[ChargingTime]
38
+ preferred_charging_times: List[ChargingTime]
39
39
  timers: List[Timer]
40
40
 
41
41
 
@@ -6,9 +6,6 @@ class ConfigurationTargetTemperature(BaseModel):
6
6
  temperature_value: float = Field(..., alias="temperatureValue")
7
7
  unit_in_car: str = Field("CELSIUS", alias="unitInCar")
8
8
 
9
- class Config:
10
- populate_by_name = True
11
-
12
9
 
13
10
  class StartAirConditioningConfiguration(BaseModel):
14
11
  """Configuration for starting air conditioning."""
@@ -19,16 +16,10 @@ class StartAirConditioningConfiguration(BaseModel):
19
16
  True, alias="airConditioningWithoutExternalPower"
20
17
  )
21
18
 
22
- class Config:
23
- populate_by_name = True
24
-
25
19
 
26
20
  class StartAuxiliaryHeatingConfiguration(BaseModel):
27
21
  """Configuration for starting auxiliary heating."""
28
22
  spin: str = Field(..., alias="spin")
29
23
  duration_in_seconds: Optional[int] = Field(120, alias="durationInSeconds")
30
24
  start_mode: Optional[str] = Field("HEATING", alias="startMode")
31
- target_temperature: Optional[ConfigurationTargetTemperature] = Field(None, alias="targetTemperature")
32
-
33
- class Config:
34
- populate_by_name = True
25
+ target_temperature: Optional[ConfigurationTargetTemperature] = Field(None, alias="targetTemperature")
@@ -102,6 +102,9 @@ class VehicleErrorState(str, Enum):
102
102
  DRIVING_RANGE_UNSUPPORTED = "DRIVING_RANGE_UNSUPPORTED"
103
103
  DRIVING_RANGE_DISABLED = "DRIVING_RANGE_DISABLED"
104
104
  DRIVING_RANGE_UNAVAILABLE = "DRIVING_RANGE_UNAVAILABLE"
105
+ FUEL_STATUS_UNSUPPORTED = "FUEL_STATUS_UNSUPPORTED"
106
+ FUEL_STATUS_DISABLED = "FUEL_STATUS_DISABLED"
107
+ FUEL_STATUS_UNAVAILABLE = "FUEL_STATUS_UNAVAILABLE"
105
108
  ODOMETER_UNSUPPORTED = "ODOMETER_UNSUPPORTED"
106
109
  ODOMETER_DISABLED = "ODOMETER_DISABLED"
107
110
  ODOMETER_UNAVAILABLE = "ODOMETER_UNAVAILABLE"
@@ -0,0 +1,63 @@
1
+ Metadata-Version: 2.4
2
+ Name: myvsb-developer-api
3
+ Version: 1.0.6
4
+ Summary: Asynchronous Python library for the MyVSB Developer API
5
+ Author-email: net0045 <stepan.netolicky@vsb.cz>
6
+ Project-URL: Homepage, https://github.com/mobility-lab-vsb/myvsb-developer-api
7
+ Project-URL: Bug Tracker, https://github.com/mobility-lab-vsb/myvsb-developer-api/issues
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Framework :: AsyncIO
12
+ Requires-Python: >=3.11
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: aiohttp>=3.9.0
16
+ Requires-Dist: pydantic>=2.5.0
17
+ Provides-Extra: test
18
+ Requires-Dist: pytest>=9.0.0; extra == "test"
19
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == "test"
20
+ Requires-Dist: aresponses>=3.0.0; extra == "test"
21
+ Provides-Extra: cli
22
+ Requires-Dist: asyncclick~=8.1; extra == "cli"
23
+ Requires-Dist: coloredlogs~=15.0; extra == "cli"
24
+ Requires-Dist: pygments~=2.18; extra == "cli"
25
+ Requires-Dist: pyyaml~=6.0; extra == "cli"
26
+ Dynamic: license-file
27
+
28
+ # vsb-developer-api
29
+
30
+ VSB developer API is a Python library to interact with the new VSB API for connected-vehicle data and remote control of VSB vehicles.
31
+
32
+ ## As CLI
33
+
34
+ The library ships with a command line interface called `skodactl`, which is useful for manually
35
+ testing the API endpoints. Install it together with the CLI dependencies:
36
+
37
+ ```sh
38
+ pip install "myvsb-developer-api[cli]"
39
+ ```
40
+
41
+ You authenticate with your API key, either via the `--api-key` option or the `SKODA_API_KEY`
42
+ environment variable, and address a vehicle by its VIN:
43
+
44
+ ```sh
45
+ skodactl --api-key XXX get-vehicle DMBGF9NY3NF032963
46
+ ```
47
+
48
+ By default, the CLI talks to the production endpoint. Point it at the public test endpoint with
49
+ `--endpoint test`, switch to YAML output with `--format yaml`, or log HTTP requests and responses
50
+ with `--trace`:
51
+
52
+ ```sh
53
+ skodactl --api-key XXX --endpoint test get-vehicle-status DMBGF9NY3NF032963
54
+ SKODA_API_KEY=XXX skodactl --format yaml --trace get-charging DMBGF9NY3NF032963
55
+ ```
56
+
57
+ Remote operations can be started as well:
58
+
59
+ ```sh
60
+ skodactl --api-key XXX start-air-conditioning DMBGF9NY3NF032963 --temperature 21
61
+ ```
62
+
63
+ Run `skodactl --help` for the full list of commands and options.
@@ -6,6 +6,11 @@ src/myskoda_openapi/api_layer/exceptions.py
6
6
  src/myskoda_openapi/api_layer/open_api_client.py
7
7
  src/myskoda_openapi/api_layer/rest_api.py
8
8
  src/myskoda_openapi/api_layer/utils.py
9
+ src/myskoda_openapi/cli/__init__.py
10
+ src/myskoda_openapi/cli/__main__.py
11
+ src/myskoda_openapi/cli/operations.py
12
+ src/myskoda_openapi/cli/requests.py
13
+ src/myskoda_openapi/cli/utils.py
9
14
  src/myskoda_openapi/models/active_ventilation.py
10
15
  src/myskoda_openapi/models/air_conditioning.py
11
16
  src/myskoda_openapi/models/auxiliary_heating.py
@@ -22,5 +27,6 @@ src/myskoda_openapi/models/vehicle_status.py
22
27
  src/myvsb_developer_api.egg-info/PKG-INFO
23
28
  src/myvsb_developer_api.egg-info/SOURCES.txt
24
29
  src/myvsb_developer_api.egg-info/dependency_links.txt
30
+ src/myvsb_developer_api.egg-info/entry_points.txt
25
31
  src/myvsb_developer_api.egg-info/requires.txt
26
32
  src/myvsb_developer_api.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ skodactl = myskoda_openapi.cli:cli
@@ -1,6 +1,12 @@
1
1
  aiohttp>=3.9.0
2
2
  pydantic>=2.5.0
3
3
 
4
+ [cli]
5
+ asyncclick~=8.1
6
+ coloredlogs~=15.0
7
+ pygments~=2.18
8
+ pyyaml~=6.0
9
+
4
10
  [test]
5
11
  pytest>=9.0.0
6
12
  pytest-asyncio>=0.23.0
@@ -1,24 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: myvsb-developer-api
3
- Version: 1.0.4
4
- Summary: Asynchronous Python library for the MyVSB Developer API
5
- Author-email: net0045 <stepan.netolicky@vsb.cz>
6
- Project-URL: Homepage, https://github.com/mobility-lab-vsb/myvsb-developer-api
7
- Project-URL: Bug Tracker, https://github.com/mobility-lab-vsb/myvsb-developer-api/issues
8
- Classifier: Programming Language :: Python :: 3
9
- Classifier: License :: OSI Approved :: MIT License
10
- Classifier: Operating System :: OS Independent
11
- Classifier: Framework :: AsyncIO
12
- Requires-Python: >=3.11
13
- Description-Content-Type: text/markdown
14
- License-File: LICENSE
15
- Requires-Dist: aiohttp>=3.9.0
16
- Requires-Dist: pydantic>=2.5.0
17
- Provides-Extra: test
18
- Requires-Dist: pytest>=9.0.0; extra == "test"
19
- Requires-Dist: pytest-asyncio>=0.23.0; extra == "test"
20
- Requires-Dist: aresponses>=3.0.0; extra == "test"
21
- Dynamic: license-file
22
-
23
- # vsb-developer-api
24
- VSB developer API is a Python library to interact with the new VSB API for connected-vehicle data and remote control of VSB vehicles.
@@ -1,2 +0,0 @@
1
- # vsb-developer-api
2
- VSB developer API is a Python library to interact with the new VSB API for connected-vehicle data and remote control of VSB vehicles.
@@ -1,4 +0,0 @@
1
- SANDBOX_BASE_URL = "https://developer.sandbox.connect.skoda-auto.cz"
2
- PRODUCTION_BASE_URL = "https://developer.connect.skoda-auto.cz"
3
- TEST_BASE_URL = "https://public.test-api.connect.skoda-auto.cz"
4
- BETA_URL = "https://public.api.connect.skoda-auto.cz"
@@ -1,24 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: myvsb-developer-api
3
- Version: 1.0.4
4
- Summary: Asynchronous Python library for the MyVSB Developer API
5
- Author-email: net0045 <stepan.netolicky@vsb.cz>
6
- Project-URL: Homepage, https://github.com/mobility-lab-vsb/myvsb-developer-api
7
- Project-URL: Bug Tracker, https://github.com/mobility-lab-vsb/myvsb-developer-api/issues
8
- Classifier: Programming Language :: Python :: 3
9
- Classifier: License :: OSI Approved :: MIT License
10
- Classifier: Operating System :: OS Independent
11
- Classifier: Framework :: AsyncIO
12
- Requires-Python: >=3.11
13
- Description-Content-Type: text/markdown
14
- License-File: LICENSE
15
- Requires-Dist: aiohttp>=3.9.0
16
- Requires-Dist: pydantic>=2.5.0
17
- Provides-Extra: test
18
- Requires-Dist: pytest>=9.0.0; extra == "test"
19
- Requires-Dist: pytest-asyncio>=0.23.0; extra == "test"
20
- Requires-Dist: aresponses>=3.0.0; extra == "test"
21
- Dynamic: license-file
22
-
23
- # vsb-developer-api
24
- VSB developer API is a Python library to interact with the new VSB API for connected-vehicle data and remote control of VSB vehicles.