tuya-vacuum 0.1.0__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.
@@ -0,0 +1,117 @@
1
+ name: Publish
2
+
3
+ on: push
4
+
5
+ jobs:
6
+ build:
7
+ name: Build distribution
8
+ runs-on: ubuntu-latest
9
+
10
+ steps:
11
+ # Download the repository into the CI runner
12
+ # Then, install and activate the newest Python 3 release
13
+ - uses: actions/checkout@v4
14
+ - name: set up Python
15
+ uses: actions/setup-python@v5
16
+ with:
17
+ python-version: "3.x"
18
+ - name: Install pypa/build
19
+ run: python3 -m pip install build --user
20
+ # Build the dists from source and store them
21
+ - name: Build a binary wheel and a source tarball
22
+ run: python3 -m build
23
+ - name: Store the distribution packages
24
+ uses: actions/upload-artifact@v4
25
+ with:
26
+ name: python-package-distributions
27
+ path: dist/
28
+
29
+ publish-to-pypi:
30
+ name: Publish distribution to PyPI
31
+ # Only publish to PyPI on tag pushes
32
+ if: startsWith(github.ref, 'refs/tags/')
33
+ needs:
34
+ - build
35
+ runs-on: ubuntu-latest
36
+ environment:
37
+ name: pypi
38
+ url: https://pypi.org/project/tuya-vacuum/
39
+ permissions:
40
+ id-token: write # IMPORTANT: mandatory for trusted publishing
41
+ steps:
42
+ - name: Download the distribution packages
43
+ uses: actions/download-artifact@v4
44
+ with:
45
+ name: python-package-distributions
46
+ path: dist/
47
+ - name: Publish distribution to PyPI
48
+ uses: pypa/gh-action-pypi-publish@release/v1
49
+
50
+ # Sign the distribution packages with Sigstore.
51
+ # Upload the signed packages to GitHub Release.
52
+ github-release:
53
+ name: Sign and upload to GitHub Release
54
+ needs:
55
+ - publish-to-pypi
56
+ runs-on: ubuntu-latest
57
+
58
+ permissions:
59
+ contents: write # IMPORTANT: mandatory for making GitHub Releases
60
+ id-token: write # IMPORTANT: mandatory for trusted publishing
61
+
62
+ steps:
63
+ - name: Download all the dists
64
+ uses: actions/download-artifact@v4
65
+ with:
66
+ name: python-package-distributions
67
+ path: dist/
68
+ - name: Sign the dists with Sigstore
69
+ uses: sigstore/gh-action-sigstore-python@v3.0.0
70
+ with:
71
+ inputs: >-
72
+ ./dist/*.tar.gz
73
+ ./dist/*.whl
74
+ - name: Create GitHub Release
75
+ env:
76
+ GITHUB_TOKEN: ${{ github.token }}
77
+ run: >-
78
+ gh release create
79
+ '${{ github.ref_name }}'
80
+ --repo '${{ github.repository }}'
81
+ --notes ""
82
+ - name: Upload artifact signatures to GitHub Release
83
+ env:
84
+ GITHUB_TOKEN: ${{ github.token }}
85
+ # Upload to GitHub Release using the `gh` CLI.
86
+ # `dist/` contains the built pacakges, adn the
87
+ # sigstore-produced signatures and certificates.
88
+ run: >-
89
+ gh release upload
90
+ '${{ github.ref_name }}' dist/**
91
+ --repo '${{ github.repository }}'
92
+
93
+
94
+ publish-to-testpypi:
95
+ name: Publish distribution to TestPyPI
96
+ needs:
97
+ - build
98
+ runs-on: ubuntu-latest
99
+
100
+ environment:
101
+ name: testpypi
102
+ url: https://test.pypi.org/p/tuya-vacuum/
103
+
104
+ permissions:
105
+ id-token: write # IMPORTANT: mandatory for trusted publishing
106
+
107
+ steps:
108
+ - name: Download the distribution packages
109
+ uses: actions/download-artifact@v4
110
+ with:
111
+ name: python-package-distributions
112
+ path: dist/
113
+ - name: Publish distribution to TestPyPI
114
+ uses: pypa/gh-action-pypi-publish@release/v1
115
+ with:
116
+ repository-url: https://test.pypi.org/legacy/
117
+ skip-existing: true # Tolerate already-published packages
@@ -0,0 +1,34 @@
1
+ name: Test
2
+
3
+ on: push
4
+
5
+ jobs:
6
+ build:
7
+ name: Build and Test
8
+ runs-on: ubuntu-latest
9
+
10
+ steps:
11
+ # Download the repository into the CI runner
12
+ # Then, install and activate the newest Python 3 release
13
+ - uses: actions/checkout@v4
14
+ - name: set up Python
15
+ uses: actions/setup-python@v5
16
+ with:
17
+ # Semantic version range syntax or exact verison of a Python version
18
+ python-version: "3.x"
19
+ - name: Install dependencies
20
+ run: |
21
+ python -m pip install --upgrade pip
22
+ pip install -r requirements.txt
23
+ - name: Install pytest
24
+ run: pip install pytest pytest-cov
25
+ - name: Test with pytest
26
+ run: pytest ./tests --doctest-modules --junitxml=junit/test-results.xml --cov=com --cov-report=xml --cov-report=html
27
+ - name: Upload pytest test results
28
+ uses: actions/upload-artifact@v4
29
+ with:
30
+ name: pytest-results
31
+ path: junit/test-results.xml
32
+ # Use always() to always run this step to publish test results.
33
+ # Even when there are test failures
34
+ if: ${{ always() }}
@@ -0,0 +1,13 @@
1
+ .env
2
+ __pycache__
3
+ .pytest_cache
4
+ venv
5
+ .coverage
6
+
7
+ # Distribution archives
8
+ dist
9
+
10
+ # Hide files made while manually testing
11
+ /layout.bin
12
+ path.bin
13
+ combined.png
@@ -0,0 +1,7 @@
1
+ {
2
+ "python.testing.pytestArgs": [
3
+ "tests"
4
+ ],
5
+ "python.testing.unittestEnabled": false,
6
+ "python.testing.pytestEnabled": true
7
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "version": "2.0.0",
3
+ "tasks": [
4
+ {
5
+ "label": "Code Coverage",
6
+ "detail": "Generate code coverage report.",
7
+ "type": "shell",
8
+ "command": "${command:python.interpreterPath}",
9
+ "args": [
10
+ "-m",
11
+ "pytest",
12
+ "--cov",
13
+ ],
14
+ "group": {
15
+ "kind": "test",
16
+ "isDefault": true
17
+ },
18
+ "presentation": {
19
+ "reveal": "always",
20
+ "panel": "dedicated"
21
+ }
22
+ }
23
+ ]
24
+ }
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Jaiden Labelle
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,62 @@
1
+ Metadata-Version: 2.3
2
+ Name: tuya-vacuum
3
+ Version: 0.1.0
4
+ Summary: A python library to view maps from Tuya robot vacuums
5
+ Project-URL: Homepage, https://github.com/jaidenlabelle/tuya-vacuum
6
+ Project-URL: Issues, https://github.com/jaidenlabelle/tuya-vacuum/issues
7
+ Author-email: Jaiden Labelle <jaidenjlabelle@gmail.com>
8
+ License-File: LICENSE
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Requires-Python: >=3.6
13
+ Description-Content-Type: text/markdown
14
+
15
+ # tuya-vacuum
16
+ tuya-vacuum is a python library to view maps from Tuya robot vacuums.
17
+
18
+ ## Installation
19
+ Use the package manager [pip](https://pip.pypa.io/en/stable/) to install tuya-vacuum.
20
+
21
+ ```bash
22
+ pip install tuya-vacuum
23
+ ```
24
+
25
+ ## Usage
26
+ ```python
27
+ from tuya_vacuum.tuya import TuyaCloudAPI
28
+ import requests
29
+
30
+ # Create a new TuyaCloudAPI instance
31
+ tuya = TuyaCloudAPI(
32
+ origin="https://openapi.tuyaus.com",
33
+ client_id="<Client ID>",
34
+ client_secret="<Client Secret>"
35
+ )
36
+
37
+ # Request the current realtime map data from the cloud
38
+ device_id = "<Device ID>"
39
+ response = tuya.request("GET", f"/v1.0/users/sweepers/file/{device_id}/realtime-map")
40
+
41
+ # Get the layout and path data using the returned map urls
42
+ layout_data = requests.get(response["result"][0]["map_url"]).content.hex()
43
+ path_data = requests.get(response["result"][1]["map_url"]).content.hex()
44
+
45
+ # Parse the map data
46
+ vacuum_map = VacuumMap(layout_data, path_data)
47
+
48
+ # Save the map as an image
49
+ image = vacuum_map.to_image()
50
+ image.save("output.png")
51
+ ```
52
+
53
+ ## Compatability List
54
+
55
+ This is a list of all currently tested devices. Create a new [issue](https://github.com/jaidenlab/tuya-vacuum/issues) to add your device.
56
+
57
+ | Device | Support |
58
+ | ----------------------------------------------------- | --------------------------------- |
59
+ | [Lefant M1](https://www.lefant.com/en-ca/products/m1) | <text style="color:lightgreen">Supported</text> |
60
+
61
+ ## Special Thanks
62
+ - [Tuya Cloud Vacuum Map Extractor](https://github.com/oven-lab/tuya_cloud_map_extractor) by [@oven-lab](https://github.com/oven-lab)
@@ -0,0 +1,48 @@
1
+ # tuya-vacuum
2
+ tuya-vacuum is a python library to view maps from Tuya robot vacuums.
3
+
4
+ ## Installation
5
+ Use the package manager [pip](https://pip.pypa.io/en/stable/) to install tuya-vacuum.
6
+
7
+ ```bash
8
+ pip install tuya-vacuum
9
+ ```
10
+
11
+ ## Usage
12
+ ```python
13
+ from tuya_vacuum.tuya import TuyaCloudAPI
14
+ import requests
15
+
16
+ # Create a new TuyaCloudAPI instance
17
+ tuya = TuyaCloudAPI(
18
+ origin="https://openapi.tuyaus.com",
19
+ client_id="<Client ID>",
20
+ client_secret="<Client Secret>"
21
+ )
22
+
23
+ # Request the current realtime map data from the cloud
24
+ device_id = "<Device ID>"
25
+ response = tuya.request("GET", f"/v1.0/users/sweepers/file/{device_id}/realtime-map")
26
+
27
+ # Get the layout and path data using the returned map urls
28
+ layout_data = requests.get(response["result"][0]["map_url"]).content.hex()
29
+ path_data = requests.get(response["result"][1]["map_url"]).content.hex()
30
+
31
+ # Parse the map data
32
+ vacuum_map = VacuumMap(layout_data, path_data)
33
+
34
+ # Save the map as an image
35
+ image = vacuum_map.to_image()
36
+ image.save("output.png")
37
+ ```
38
+
39
+ ## Compatability List
40
+
41
+ This is a list of all currently tested devices. Create a new [issue](https://github.com/jaidenlab/tuya-vacuum/issues) to add your device.
42
+
43
+ | Device | Support |
44
+ | ----------------------------------------------------- | --------------------------------- |
45
+ | [Lefant M1](https://www.lefant.com/en-ca/products/m1) | <text style="color:lightgreen">Supported</text> |
46
+
47
+ ## Special Thanks
48
+ - [Tuya Cloud Vacuum Map Extractor](https://github.com/oven-lab/tuya_cloud_map_extractor) by [@oven-lab](https://github.com/oven-lab)
@@ -0,0 +1,22 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "tuya-vacuum"
7
+ version = "0.1.0"
8
+ description = "A python library to view maps from Tuya robot vacuums"
9
+ authors = [
10
+ { name = "Jaiden Labelle", email = "jaidenjlabelle@gmail.com" }
11
+ ]
12
+ readme = "README.md"
13
+ requires-python = ">=3.6"
14
+ classifiers = [
15
+ "License :: OSI Approved :: MIT License",
16
+ "Programming Language :: Python :: 3",
17
+ "Operating System :: OS Independent",
18
+ ]
19
+
20
+ [project.urls]
21
+ Homepage = "https://github.com/jaidenlabelle/tuya-vacuum"
22
+ Issues = "https://github.com/jaidenlabelle/tuya-vacuum/issues"
Binary file
@@ -0,0 +1,49 @@
1
+ """Script to download the current realtime map from a vacuum using the Tuya Cloud API."""
2
+
3
+ import os
4
+
5
+ import requests
6
+ from dotenv import load_dotenv
7
+ from tuya_vacuum.tuya import TuyaCloudAPI
8
+
9
+ # Load environment variables
10
+ load_dotenv()
11
+
12
+ # Get environment variables
13
+ CLIENT_ID = os.environ["CLIENT_ID"]
14
+ CLIENT_SECRET = os.environ["CLIENT_SECRET"]
15
+ DEVICE_ID = os.environ["DEVICE_ID"]
16
+
17
+
18
+ def main():
19
+ """Download the current realtime map from a vacuum using the Tuya Cloud API."""
20
+
21
+ BASE = "https://openapi.tuyaus.com"
22
+ ENDPOINT = f"/v1.0/users/sweepers/file/{DEVICE_ID}/realtime-map"
23
+
24
+ tuya = TuyaCloudAPI(base=BASE, client_id=CLIENT_ID, client_secret=CLIENT_SECRET)
25
+ response = tuya.request(ENDPOINT)
26
+
27
+ maps = response["result"]
28
+
29
+ for vacuum_map in maps:
30
+ print(vacuum_map)
31
+
32
+ map_url = vacuum_map["map_url"]
33
+ map_data = requests.get(map_url, timeout=2.5).content
34
+
35
+ if vacuum_map["map_type"] == 1:
36
+ # Save Path Data
37
+ with open("path.bin", "wb") as file:
38
+ file.write(map_data)
39
+ if vacuum_map["map_type"] == 0:
40
+ # Save Map Data
41
+ with open("layout.bin", "wb") as file:
42
+ file.write(map_data)
43
+ else:
44
+ # Unknown Map Type
45
+ print(f"Unknown Map Type: {vacuum_map['map_type']}")
46
+
47
+
48
+ if __name__ == "__main__":
49
+ main()
@@ -0,0 +1,36 @@
1
+ """Script to parse data from a vacuum map."""
2
+
3
+ import logging
4
+
5
+ from tuya_vacuum.vacuum_map import VacuumMap
6
+
7
+
8
+ logging.basicConfig(level=logging.DEBUG)
9
+
10
+
11
+ def main():
12
+ """Parse data from a vacuum map."""
13
+
14
+ with open("path.bin", "rb") as path_file:
15
+ with open("layout.bin", "rb") as layout_file:
16
+ # Read each file as a hex string
17
+ layout_data = layout_file.read().hex()
18
+ path_data = path_file.read().hex()
19
+
20
+ vacuum_map = VacuumMap(layout_data, path_data)
21
+ map_image = vacuum_map.to_image()
22
+ map_image = VacuumMap.crop_image(
23
+ map_image,
24
+ 410,
25
+ 250,
26
+ vacuum_map.layout.origin_x,
27
+ vacuum_map.layout.origin_y,
28
+ offset_x=50,
29
+ offset_y=60,
30
+ )
31
+
32
+ map_image.save("combined.png")
33
+
34
+
35
+ if __name__ == "__main__":
36
+ main()
File without changes
Binary file
@@ -0,0 +1,209 @@
1
+ """Functional test for communication with the Tuya Cloud API."""
2
+
3
+ import logging
4
+ from uuid import UUID
5
+ import httpx
6
+ from pytest_httpx import HTTPXMock
7
+ from pytest_mock import MockerFixture
8
+ from freezegun import freeze_time
9
+ import pytest
10
+
11
+ from tuya_vacuum.tuya import CrossRegionAccessError, InvalidClientIDError, InvalidDeviceIDError, TuyaCloudAPI, InvalidClientSecretError
12
+
13
+ _LOGGER = logging.getLogger(__name__)
14
+
15
+ CLIENT_ID = "example"
16
+ CLIENT_SECRET = "example"
17
+ DEVICE_ID = "example"
18
+ ORIGIN = "https://openapi.tuyaus.com"
19
+ ENDPOINT = f"/v1.0/users/sweepers/file/{DEVICE_ID}/realtime-map"
20
+
21
+ # Mock the datetime module to a fixed time
22
+ @freeze_time("2024-11-02 12:00:01")
23
+ def test_get_timestamp():
24
+ """Test TuyaCloudAPI.get_timestamp."""
25
+
26
+ assert TuyaCloudAPI.get_timestamp() == "1730548801000"
27
+
28
+ def test_get_nonce(mocker: MockerFixture):
29
+ """Test TuyaCloudAPI.get_nonce."""
30
+
31
+ # Mock the UUID generation
32
+ mocker.patch("uuid.uuid4", return_value=UUID('0e950a25-9a73-4b8e-bae7-86f131450350'))
33
+
34
+ assert TuyaCloudAPI.get_nonce() == "0e950a259a734b8ebae786f131450350"
35
+
36
+ def test_create_signature():
37
+ """Test TuyaCloudAPI.create_signature."""
38
+ tuya = TuyaCloudAPI(origin=ORIGIN, client_id=CLIENT_ID, client_secret=CLIENT_SECRET)
39
+
40
+ method = "GET"
41
+ endpoint = ENDPOINT
42
+ timestamp = "1730548801000"
43
+ nonce = "0e950a259a734b8ebae786f131450350"
44
+ access_token = "example_access_token"
45
+
46
+ tuya = TuyaCloudAPI(origin=ORIGIN, client_id=CLIENT_ID, client_secret=CLIENT_SECRET)
47
+ signature = tuya.create_signature(method, endpoint, timestamp, nonce, access_token)
48
+
49
+ assert signature == "7ADE4474E49D93106F236A261C539CFEC7C89801047896F5A3376202B422A8FD"
50
+
51
+ # Mock the datetime module to a fixed time
52
+ @freeze_time("2024-11-02 12:00:01")
53
+ def test_request(mocker: MockerFixture, httpx_mock: HTTPXMock):
54
+ """Test TuyaCloudAPI.request."""
55
+
56
+ # Mock response for the request to get the access token
57
+ httpx_mock.add_response(
58
+ url = f"{ORIGIN}/v1.0/token?grant_type=1",
59
+ json = {
60
+ "success": True,
61
+ "result": {
62
+ "access_token": "example_access_token",
63
+ }
64
+ }
65
+ )
66
+ # Mock response for the request to get the map URLs
67
+ httpx_mock.add_response(
68
+ url = f"{ORIGIN}{ENDPOINT}",
69
+ json = {
70
+ "success": True,
71
+ "result": [
72
+ {
73
+ "map_url": "example_layout_map_url",
74
+ "map_type": 0
75
+ },
76
+ {
77
+ "map_url": "example_path_map_url",
78
+ "map_type": 1
79
+ }
80
+ ]
81
+ }
82
+ )
83
+
84
+ # Mock the UUID generation
85
+ mocker.patch("uuid.uuid4", return_value=UUID('0e950a25-9a73-4b8e-bae7-86f131450350'))
86
+
87
+ with httpx.Client() as client:
88
+ # Create a TuyaCloudAPI instance
89
+ tuya = TuyaCloudAPI(
90
+ origin=ORIGIN,
91
+ client_id=CLIENT_ID,
92
+ client_secret=CLIENT_SECRET,
93
+ client=client
94
+ )
95
+
96
+ response = tuya.request("GET", ENDPOINT)
97
+
98
+ # Check if the returned values are correct
99
+ # Check if the response is successful
100
+ assert response["success"]
101
+ # Check if the layout map URL is correct
102
+ assert response["result"][0]["map_url"] == "example_layout_map_url"
103
+ # Check if the path map URL is correct
104
+ assert response["result"][1]["map_url"] == "example_path_map_url"
105
+
106
+ requests = httpx_mock.get_requests()
107
+
108
+ # Check if the access token request was successful
109
+ request = requests[0]
110
+ headers = request.headers
111
+ assert request.url == f"{ORIGIN}/v1.0/token?grant_type=1"
112
+ assert headers.get("client_id") == CLIENT_ID
113
+ assert headers.get("sign") == (
114
+ "B557C364FD291EFF020769E5D1D1689C7C9BAF9AFAA7D936CCA26361D01B17DE"
115
+ )
116
+ assert headers.get("sign_method") == "HMAC-SHA256"
117
+ assert headers.get("t") == "1730548801000"
118
+ assert headers.get("lang") == "en"
119
+ assert headers.get("nonce") == "0e950a259a734b8ebae786f131450350"
120
+
121
+ # Check if the endpoint request was successful
122
+ request = requests[1]
123
+ headers = request.headers
124
+ assert request.url == f"{ORIGIN}{ENDPOINT}"
125
+ assert headers.get("client_id") == CLIENT_ID
126
+ assert headers.get("sign") == (
127
+ "7ADE4474E49D93106F236A261C539CFEC7C89801047896F5A3376202B422A8FD"
128
+ )
129
+ assert headers.get("sign_method") == "HMAC-SHA256"
130
+ assert headers.get("t") == "1730548801000"
131
+ assert headers.get("lang") == "en"
132
+ assert headers.get("nonce") == "0e950a259a734b8ebae786f131450350"
133
+
134
+ def test_invalid_request(httpx_mock: HTTPXMock):
135
+ """Test TuyaCloudAPI.request with an invalid request."""
136
+
137
+ # Invalid client id request
138
+ httpx_mock.add_response(
139
+ url = f"{ORIGIN}/v1.0/token?grant_type=1",
140
+ match_headers={"client_id": "wrong_client_id"},
141
+ json = {
142
+ "success": False,
143
+ "code": 1005,
144
+ }
145
+ )
146
+
147
+
148
+ # Invalid origin
149
+ httpx_mock.add_response(
150
+ url = "https://invalid_origin.com/v1.0/token?grant_type=1",
151
+ json = {
152
+ "success": False,
153
+ "code": 2007,
154
+ }
155
+ )
156
+
157
+ # Mock response for the request to get the map URLs
158
+ httpx_mock.add_response(
159
+ url = f"{ORIGIN}/invalid_device_id/realtime-map",
160
+ json = {
161
+ "success": False,
162
+ "code": 1106
163
+ }
164
+ )
165
+
166
+ with httpx.Client() as client:
167
+ # # Create a TuyaCloudAPI instance
168
+ # tuya = TuyaCloudAPI(
169
+ # origin=ORIGIN,
170
+ # client_id=CLIENT_ID,
171
+ # client_secret="wrong_client_secret",
172
+ # client=client
173
+ # )
174
+
175
+ # with pytest.raises(InvalidClientSecretError):
176
+ # tuya.request("GET", "/v1.0/token?grant_type=1", fetch_token=False)
177
+
178
+ # Create a TuyaCloudAPI instance
179
+ tuya = TuyaCloudAPI(
180
+ origin=ORIGIN,
181
+ client_id="wrong_client_id",
182
+ client_secret=CLIENT_SECRET,
183
+ client=client
184
+ )
185
+
186
+ with pytest.raises(InvalidClientIDError):
187
+ tuya.request("GET", "/v1.0/token?grant_type=1", fetch_token=False)
188
+
189
+ # Create a TuyaCloudAPI instance
190
+ tuya = TuyaCloudAPI(
191
+ origin="https://invalid_origin.com",
192
+ client_id=CLIENT_ID,
193
+ client_secret=CLIENT_SECRET,
194
+ client=client
195
+ )
196
+
197
+ with pytest.raises(CrossRegionAccessError):
198
+ tuya.request("GET", "/v1.0/token?grant_type=1", fetch_token=False)
199
+
200
+ # Create a TuyaCloudAPI instance
201
+ tuya = TuyaCloudAPI(
202
+ origin=ORIGIN,
203
+ client_id=CLIENT_ID,
204
+ client_secret=CLIENT_SECRET,
205
+ client=client
206
+ )
207
+
208
+ with pytest.raises(InvalidDeviceIDError):
209
+ tuya.request("GET", "/invalid_device_id/realtime-map", fetch_token=False)