standardbots 2.0.0.dev1736549072__tar.gz → 2.0.0.dev1737492400__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.
Potentially problematic release.
This version of standardbots might be problematic. Click here for more details.
- {standardbots-2.0.0.dev1736549072 → standardbots-2.0.0.dev1737492400}/PKG-INFO +1 -1
- standardbots-2.0.0.dev1737492400/README.md +141 -0
- {standardbots-2.0.0.dev1736549072 → standardbots-2.0.0.dev1737492400}/setup.py +1 -1
- {standardbots-2.0.0.dev1736549072 → standardbots-2.0.0.dev1737492400}/standardbots/auto_generated/models.py +9 -7
- {standardbots-2.0.0.dev1736549072 → standardbots-2.0.0.dev1737492400}/standardbots.egg-info/PKG-INFO +1 -1
- {standardbots-2.0.0.dev1736549072 → standardbots-2.0.0.dev1737492400}/standardbots.egg-info/SOURCES.txt +6 -1
- {standardbots-2.0.0.dev1736549072 → standardbots-2.0.0.dev1737492400}/standardbots.egg-info/top_level.txt +1 -0
- standardbots-2.0.0.dev1737492400/tests/fixtures/__init__.py +0 -0
- standardbots-2.0.0.dev1737492400/tests/fixtures/client_fixt.py +25 -0
- standardbots-2.0.0.dev1737492400/tests/fixtures/routines_fixt.py +48 -0
- standardbots-2.0.0.dev1737492400/tests/test_apis.py +1294 -0
- {standardbots-2.0.0.dev1736549072 → standardbots-2.0.0.dev1737492400}/setup.cfg +0 -0
- {standardbots-2.0.0.dev1736549072 → standardbots-2.0.0.dev1737492400}/standardbots/__init__.py +0 -0
- {standardbots-2.0.0.dev1736549072 → standardbots-2.0.0.dev1737492400}/standardbots/auto_generated/__init__.py +0 -0
- {standardbots-2.0.0.dev1736549072 → standardbots-2.0.0.dev1737492400}/standardbots/auto_generated/apis.py +0 -0
- {standardbots-2.0.0.dev1736549072 → standardbots-2.0.0.dev1737492400}/standardbots.egg-info/dependency_links.txt +0 -0
- {standardbots-2.0.0.dev1736549072 → standardbots-2.0.0.dev1737492400}/standardbots.egg-info/requires.txt +0 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
You can test the API by using the playground, add your own file or use the existing python scripts.
|
|
2
|
+
When a client has an issue, and they send their code. It's helpful to test their code to see possible issues
|
|
3
|
+
|
|
4
|
+
# ENABLE API ON YOUR LOCAL MACHINE
|
|
5
|
+
|
|
6
|
+
Follow the steps here https://www.notion.so/standardbots/Using-the-REST-API-b2c778d47969444dac61483f0117acad
|
|
7
|
+
|
|
8
|
+
# CONFIG
|
|
9
|
+
|
|
10
|
+
At the top of the file, use your token
|
|
11
|
+
|
|
12
|
+
# RUN
|
|
13
|
+
|
|
14
|
+
To run a script move into the `sdks/python` folder and run `python playground/filename.py`
|
|
15
|
+
|
|
16
|
+
# To create a test build
|
|
17
|
+
|
|
18
|
+
1. Update version in `setup.py`
|
|
19
|
+
2. Run `python3 setup.py sdist bdist_wheel`
|
|
20
|
+
|
|
21
|
+
# Tests
|
|
22
|
+
|
|
23
|
+
## Setup
|
|
24
|
+
|
|
25
|
+
To set up tests:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
cd sdks/python
|
|
29
|
+
|
|
30
|
+
pip install -r requirements.txt
|
|
31
|
+
pip install -r requirements-dev.txt
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Create sample data
|
|
35
|
+
|
|
36
|
+
#### Sample routine
|
|
37
|
+
|
|
38
|
+
You need to add the sample routine in [sdks/python/tests/fixtures/test_public_api_routine.json](./tests/fixtures/test_public_api_routine.json) to your target test environment (i.e. upload the routine to ).
|
|
39
|
+
|
|
40
|
+
The name of routine should be "Test Public API"
|
|
41
|
+
|
|
42
|
+
#### Sample globals
|
|
43
|
+
|
|
44
|
+
- _Variable._ Add global variable called "test_public_api_global" with any value.
|
|
45
|
+
- _Space._ Create a global space called "Test global space" of any kind.
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
## Running
|
|
49
|
+
|
|
50
|
+
Here is a basic test command:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
SB_API_URL=http://34.162.0.32:3000
|
|
54
|
+
SB_API_TOKEN=...
|
|
55
|
+
|
|
56
|
+
python3 -m pytest ./tests --cov=standardbots --token=$SB_API_TOKEN --api-url=$SB_API_URL
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
You may also set up a `.env` file at `sdks/python/.env` with the following contents:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
export SB_API_URL=http://34.162.0.32:3000
|
|
63
|
+
export SB_API_TOKEN=...
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Then you can just do:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
python3 -m pytest ./tests --cov=standardbots
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Robot state and testing (Markers)
|
|
73
|
+
|
|
74
|
+
We need the bot to be in a certain state to run certain tests. For example, we need a routine to be running in order to stop the routine.
|
|
75
|
+
|
|
76
|
+
At start of testing, robot should:
|
|
77
|
+
|
|
78
|
+
- _NOT_ be e-stopped.
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
The basic idea here is:
|
|
82
|
+
|
|
83
|
+
- These special tests will not be run by default.
|
|
84
|
+
- You may pass a flag (e.g. `--routine-running`) when the bot is in the correct state to run the tests.
|
|
85
|
+
- When the flag is passed:
|
|
86
|
+
1. Tests with the flag are run.
|
|
87
|
+
2. Tests without the flag are not run.
|
|
88
|
+
|
|
89
|
+
We use [pytest markers](https://docs.pytest.org/en/7.1.x/example/markers.html) to do this.
|
|
90
|
+
|
|
91
|
+
#### Routine running
|
|
92
|
+
|
|
93
|
+
The special sample routine ("Test Public API") should be running prior to running these tests. Then do:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
python3 -m pytest ./tests --cov=standardbots --routine-running
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
#### E-stop
|
|
100
|
+
|
|
101
|
+
No marker needed for e-stop. However, we do rely on active recovery of e-stop and getting the failure state in order to do these tests.
|
|
102
|
+
|
|
103
|
+
When e-stop test runs, cannot have bot in a failure state (pre-test will fail).
|
|
104
|
+
|
|
105
|
+
## Troubleshooting
|
|
106
|
+
|
|
107
|
+
### Tests are hanging
|
|
108
|
+
|
|
109
|
+
The first test appears to start but then nothing happens for several seconds:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
$ python3 -m pytest ./tests --cov=standardbots
|
|
113
|
+
========================================================================================================== test session starts ===========================================================================================================
|
|
114
|
+
platform linux -- Python 3.10.12, pytest-6.2.5, py-1.10.0, pluggy-0.13.0
|
|
115
|
+
rootdir: /workspaces/sb/sdks/python, configfile: pytest.ini
|
|
116
|
+
plugins: ament-pep257-0.12.11, ament-xmllint-0.12.11, launch-testing-1.0.6, launch-testing-ros-0.19.7, ament-flake8-0.12.11, ament-lint-0.12.11, ament-copyright-0.12.11, colcon-core-0.18.1, cov-6.0.0
|
|
117
|
+
collected 110 items
|
|
118
|
+
|
|
119
|
+
tests/test_apis.py
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Fixes:
|
|
123
|
+
|
|
124
|
+
- _Make sure you can log into remote control._ Ensure that botman is connected.
|
|
125
|
+
- _Ensure that the robot URL is up-to-date._ Botman url will often change when you reboot.
|
|
126
|
+
|
|
127
|
+
### Custom sensors
|
|
128
|
+
|
|
129
|
+
To test custom sensors:
|
|
130
|
+
- go to the menu on the left bottom corner;
|
|
131
|
+
- Click on 'Equipment';
|
|
132
|
+
- Add Gripper > Custom Gripper;
|
|
133
|
+
- Go to the Sensors tab and click 'Add Sensor';
|
|
134
|
+
- Keep the default values as they are (name: 'Sensor 1', kind: 'Control Box IO', sensor value: 'low');
|
|
135
|
+
- Hit 'Save' and make sure the Custom Gripper is enabled.
|
|
136
|
+
|
|
137
|
+
Then run:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
python3 -m pytest ./tests --cov=standardbots --custom-sensors
|
|
141
|
+
```
|
|
@@ -1087,6 +1087,8 @@ class ErrorEnum(Enum):
|
|
|
1087
1087
|
"""Requested resource not found"""
|
|
1088
1088
|
InvalidSpaceSpecified = "invalid_space_specified"
|
|
1089
1089
|
"""Space specified was invalid or not found"""
|
|
1090
|
+
InvalidParameters = "invalid_parameters"
|
|
1091
|
+
"""Parameters are invalid"""
|
|
1090
1092
|
|
|
1091
1093
|
def parse_error_enum(data: object) -> ErrorEnum:
|
|
1092
1094
|
return ErrorEnum(data)
|
|
@@ -3037,7 +3039,7 @@ class ErrorResponse:
|
|
|
3037
3039
|
if value is None:
|
|
3038
3040
|
return [False, "error is required for ErrorResponse"]
|
|
3039
3041
|
|
|
3040
|
-
if not ((isinstance(value, str) and ErrorEnum in ['authorization_required', 'routine_must_be_running', 'api_control_required', 'robot_brakes_disengage_failed', 'robot_brakes_engage_failed', 'request_failed_validation', 'robot_not_idle', 'brakes_must_be_engaged', 'brakes_must_be_disengaged', 'equipment_no_matching', 'service_initializing', 'camera_disconnected', 'settings_validation_error', 'settings_timeout', 'internal_server_error', 'recovery_error', 'not_found', 'invalid_space_specified']) or isinstance(value, ErrorEnum)):
|
|
3042
|
+
if not ((isinstance(value, str) and ErrorEnum in ['authorization_required', 'routine_must_be_running', 'api_control_required', 'robot_brakes_disengage_failed', 'robot_brakes_engage_failed', 'request_failed_validation', 'robot_not_idle', 'brakes_must_be_engaged', 'brakes_must_be_disengaged', 'equipment_no_matching', 'service_initializing', 'camera_disconnected', 'settings_validation_error', 'settings_timeout', 'internal_server_error', 'recovery_error', 'not_found', 'invalid_space_specified', 'invalid_parameters']) or isinstance(value, ErrorEnum)):
|
|
3041
3043
|
return [False, "error must be of type ErrorEnum for ErrorResponse, got " + type(value).__name__]
|
|
3042
3044
|
|
|
3043
3045
|
return [True, ""]
|
|
@@ -5935,7 +5937,7 @@ class Space:
|
|
|
5935
5937
|
kind: Union[str, None] = None
|
|
5936
5938
|
name: Union[str, None] = None
|
|
5937
5939
|
description: Union[str, None] = None
|
|
5938
|
-
|
|
5940
|
+
is_global: Union[bool, None] = None
|
|
5939
5941
|
positions: Union[SpacePositionsMap, None] = None
|
|
5940
5942
|
|
|
5941
5943
|
def validate_id(self, value: str) -> Tuple[bool, str]:
|
|
@@ -5974,12 +5976,12 @@ class Space:
|
|
|
5974
5976
|
|
|
5975
5977
|
return [True, ""]
|
|
5976
5978
|
|
|
5977
|
-
def
|
|
5979
|
+
def validate_is_global(self, value: bool) -> Tuple[bool, str]:
|
|
5978
5980
|
if value is None:
|
|
5979
5981
|
return [True, ""]
|
|
5980
5982
|
|
|
5981
5983
|
if not isinstance(value, bool):
|
|
5982
|
-
return [False, "
|
|
5984
|
+
return [False, "is_global must be of type bool for Space, got " + type(value).__name__]
|
|
5983
5985
|
|
|
5984
5986
|
return [True, ""]
|
|
5985
5987
|
|
|
@@ -6006,7 +6008,7 @@ class Space:
|
|
|
6006
6008
|
is_valid, error_str = self.validate_description(self.description)
|
|
6007
6009
|
if not is_valid:
|
|
6008
6010
|
raise TypeError(error_str)
|
|
6009
|
-
is_valid, error_str = self.
|
|
6011
|
+
is_valid, error_str = self.validate_is_global(self.is_global)
|
|
6010
6012
|
if not is_valid:
|
|
6011
6013
|
raise TypeError(error_str)
|
|
6012
6014
|
is_valid, error_str = self.validate_positions(self.positions)
|
|
@@ -6019,7 +6021,7 @@ def parse_space(data: object):
|
|
|
6019
6021
|
kind=parse_str(data["kind"]) if "kind" in data and data.get("kind") is not None else None,
|
|
6020
6022
|
name=parse_str(data["name"]) if "name" in data and data.get("name") is not None else None,
|
|
6021
6023
|
description=parse_str(data["description"]) if "description" in data and data.get("description") is not None else None,
|
|
6022
|
-
|
|
6024
|
+
is_global=parse_bool(data["is_global"]) if "is_global" in data and data.get("is_global") is not None else None,
|
|
6023
6025
|
positions=parse_space_positions_map(data["positions"]) if "positions" in data and data.get("positions") is not None else None,
|
|
6024
6026
|
)
|
|
6025
6027
|
|
|
@@ -6029,7 +6031,7 @@ def serialize_space(data: Space) -> object:
|
|
|
6029
6031
|
"kind": None if data.kind is None else serialize_str(data.kind),
|
|
6030
6032
|
"name": None if data.name is None else serialize_str(data.name),
|
|
6031
6033
|
"description": None if data.description is None else serialize_str(data.description),
|
|
6032
|
-
"
|
|
6034
|
+
"is_global": None if data.is_global is None else serialize_bool(data.is_global),
|
|
6033
6035
|
"positions": None if data.positions is None else serialize_space_positions_map(data.positions),
|
|
6034
6036
|
}
|
|
6035
6037
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
README.md
|
|
1
2
|
setup.py
|
|
2
3
|
standardbots/__init__.py
|
|
3
4
|
standardbots.egg-info/PKG-INFO
|
|
@@ -7,4 +8,8 @@ standardbots.egg-info/requires.txt
|
|
|
7
8
|
standardbots.egg-info/top_level.txt
|
|
8
9
|
standardbots/auto_generated/__init__.py
|
|
9
10
|
standardbots/auto_generated/apis.py
|
|
10
|
-
standardbots/auto_generated/models.py
|
|
11
|
+
standardbots/auto_generated/models.py
|
|
12
|
+
tests/test_apis.py
|
|
13
|
+
tests/fixtures/__init__.py
|
|
14
|
+
tests/fixtures/client_fixt.py
|
|
15
|
+
tests/fixtures/routines_fixt.py
|
|
File without changes
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Fixtures: API Clients"""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from standardbots import StandardBotsRobot
|
|
7
|
+
from standardbots.auto_generated.apis import RobotKind
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.fixture(scope="session")
|
|
11
|
+
def client_live(api_url: str, api_token: str):
|
|
12
|
+
"""StandardBotsRobot API client - Live mode"""
|
|
13
|
+
return StandardBotsRobot(
|
|
14
|
+
url=api_url,
|
|
15
|
+
token=api_token,
|
|
16
|
+
robot_kind=RobotKind.Live,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@pytest.fixture(scope="session")
|
|
21
|
+
def client_sim(api_url: str, api_token: str):
|
|
22
|
+
"""StandardBotsRobot API client - Simulated mode"""
|
|
23
|
+
return StandardBotsRobot(
|
|
24
|
+
url=api_url, token=api_token, robot_kind=RobotKind.Simulated
|
|
25
|
+
)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Fixtures: Routines"""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from standardbots import StandardBotsRobot
|
|
5
|
+
from standardbots.auto_generated import models
|
|
6
|
+
|
|
7
|
+
SAMPLE_ROUTINE_NAME = "Test Public API"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.fixture(scope="session")
|
|
11
|
+
def routine_sample(client_live: StandardBotsRobot) -> models.Routine:
|
|
12
|
+
"""Fixture: Get a sample routine.
|
|
13
|
+
|
|
14
|
+
Relies on `StandardBotsRobot#routine_editor#routines#list`
|
|
15
|
+
"""
|
|
16
|
+
limit = 100
|
|
17
|
+
offset = 0
|
|
18
|
+
i = 0
|
|
19
|
+
while i < 100:
|
|
20
|
+
try:
|
|
21
|
+
with client_live.connection():
|
|
22
|
+
res = client_live.routine_editor.routines.list(
|
|
23
|
+
limit=limit, offset=offset
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
data = res.ok()
|
|
27
|
+
except Exception as e:
|
|
28
|
+
raise ValueError("Failed to fetch a sample routine.") from e
|
|
29
|
+
|
|
30
|
+
if len(data.items) == 0:
|
|
31
|
+
break
|
|
32
|
+
|
|
33
|
+
routines = [r for r in data.items if r.name == SAMPLE_ROUTINE_NAME]
|
|
34
|
+
if len(routines) != 0:
|
|
35
|
+
return routines[0]
|
|
36
|
+
|
|
37
|
+
offset += limit
|
|
38
|
+
i += 1
|
|
39
|
+
|
|
40
|
+
raise ValueError(
|
|
41
|
+
f"Failed to find a routine named '{SAMPLE_ROUTINE_NAME}'. Please create a routine with this name in order to continue testing."
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@pytest.fixture(scope="session")
|
|
46
|
+
def routine_sample_id(routine_sample: models.Routine) -> str:
|
|
47
|
+
"""Fixture: ID of sample routine"""
|
|
48
|
+
return routine_sample.id
|