gbg-remote-sdk 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,46 @@
1
+ Metadata-Version: 2.4
2
+ Name: gbg-remote-sdk
3
+ Version: 0.1.0
4
+ Summary: Software Development Kit for the SOAP-based API for Green Button Go Remote Control
5
+ Author: Eli Fine
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: Programming Language :: Python :: 3.12
8
+ Classifier: Programming Language :: Python :: 3.13
9
+ Requires-Python: >=3.12.7
10
+ Description-Content-Type: text/markdown
11
+ Requires-Dist: zeep>=4.3.2
12
+ Requires-Dist: pydantic>=2.11.7
13
+ Requires-Dist: requests>=2.32.5
14
+
15
+ [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://github.com/pre-commit/pre-commit)
16
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
17
+ [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
18
+ [![Checked with pyright](https://microsoft.github.io/pyright/img/pyright_badge.svg)](https://microsoft.github.io/pyright/)
19
+ [![Actions status](https://github.com/lab-sync/gbg-remote-sdk/actions/workflows/ci.yaml/badge.svg?branch=main)](https://github.com/lab-sync/gbg-remote-sdk/actions)
20
+ [![Open in Dev Containers](https://img.shields.io/static/v1?label=Dev%20Containers&message=Open&color=blue)](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/lab-sync/gbg-remote-sdk)
21
+ [![PyPI Version](https://img.shields.io/pypi/v/gbg-remote-sdk.svg)](https://pypi.org/project/gbg-remote-sdk/)
22
+ [![Downloads](https://pepy.tech/badge/gbg-remote-sdk)](https://pepy.tech/project/gbg-remote-sdk)
23
+ [![Python Versions](https://img.shields.io/pypi/pyversions/gbg-remote-sdk.svg)](https://pypi.org/project/gbg-remote-sdk/)
24
+ [![Codecov](https://codecov.io/gh/lab-sync/gbg-remote-sdk/branch/main/graph/badge.svg)](https://codecov.io/gh/lab-sync/gbg-remote-sdk)
25
+
26
+ # Usage
27
+ Documentation is hosted on [ReadTheDocs](https://gbg-remote-sdk.readthedocs.io/en/latest/?badge=latest).
28
+
29
+ # Development
30
+ This project has a dev container. If you already have VS Code and Docker installed, you can click the badge above or [here](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/lab-sync/gbg-remote-sdk) to get started. Clicking these links will cause VS Code to automatically install the Dev Containers extension if needed, clone the source code into a container volume, and spin up a dev container for use.
31
+
32
+ To publish a new version of the repository, you can run the `Publish` workflow manually and publish to the staging registry from any branch, and you can check the 'Publish to Primary' option when on `main` to publish to the primary registry and create a git tag.
33
+
34
+ ### For doing testing of the GBG Remote interface:
35
+ Open a connection to the EC2 instance hosting GBG in AWS:
36
+ ```bash
37
+ aws ssm start-session --target i-033eef3ccfbe82829 --document-name AWS-StartPortForwardingSession --parameters '{"portNumber":["8080"],"localPortNumber":["8080"]}'
38
+ ```
39
+
40
+ ### When running the unit test suite, `pytest-recording` library is used. If it gives you errors about a cassette missing, the parameter to invoke a live HTTP request is `--record-mode=once` (add on to the end of the `pytest` command)
41
+
42
+
43
+
44
+ ## Updating from the template
45
+ This repository uses a copier template. To pull in the latest updates from the template, use the command:
46
+ `copier update --trust --conflict rej --defaults`
@@ -0,0 +1,32 @@
1
+ [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://github.com/pre-commit/pre-commit)
2
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
3
+ [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
4
+ [![Checked with pyright](https://microsoft.github.io/pyright/img/pyright_badge.svg)](https://microsoft.github.io/pyright/)
5
+ [![Actions status](https://github.com/lab-sync/gbg-remote-sdk/actions/workflows/ci.yaml/badge.svg?branch=main)](https://github.com/lab-sync/gbg-remote-sdk/actions)
6
+ [![Open in Dev Containers](https://img.shields.io/static/v1?label=Dev%20Containers&message=Open&color=blue)](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/lab-sync/gbg-remote-sdk)
7
+ [![PyPI Version](https://img.shields.io/pypi/v/gbg-remote-sdk.svg)](https://pypi.org/project/gbg-remote-sdk/)
8
+ [![Downloads](https://pepy.tech/badge/gbg-remote-sdk)](https://pepy.tech/project/gbg-remote-sdk)
9
+ [![Python Versions](https://img.shields.io/pypi/pyversions/gbg-remote-sdk.svg)](https://pypi.org/project/gbg-remote-sdk/)
10
+ [![Codecov](https://codecov.io/gh/lab-sync/gbg-remote-sdk/branch/main/graph/badge.svg)](https://codecov.io/gh/lab-sync/gbg-remote-sdk)
11
+
12
+ # Usage
13
+ Documentation is hosted on [ReadTheDocs](https://gbg-remote-sdk.readthedocs.io/en/latest/?badge=latest).
14
+
15
+ # Development
16
+ This project has a dev container. If you already have VS Code and Docker installed, you can click the badge above or [here](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/lab-sync/gbg-remote-sdk) to get started. Clicking these links will cause VS Code to automatically install the Dev Containers extension if needed, clone the source code into a container volume, and spin up a dev container for use.
17
+
18
+ To publish a new version of the repository, you can run the `Publish` workflow manually and publish to the staging registry from any branch, and you can check the 'Publish to Primary' option when on `main` to publish to the primary registry and create a git tag.
19
+
20
+ ### For doing testing of the GBG Remote interface:
21
+ Open a connection to the EC2 instance hosting GBG in AWS:
22
+ ```bash
23
+ aws ssm start-session --target i-033eef3ccfbe82829 --document-name AWS-StartPortForwardingSession --parameters '{"portNumber":["8080"],"localPortNumber":["8080"]}'
24
+ ```
25
+
26
+ ### When running the unit test suite, `pytest-recording` library is used. If it gives you errors about a cassette missing, the parameter to invoke a live HTTP request is `--record-mode=once` (add on to the end of the `pytest` command)
27
+
28
+
29
+
30
+ ## Updating from the template
31
+ This repository uses a copier template. To pull in the latest updates from the template, use the command:
32
+ `copier update --trust --conflict rej --defaults`
@@ -0,0 +1,55 @@
1
+ [project]
2
+ name = "gbg-remote-sdk"
3
+ version = "0.1.0"
4
+ description = "Software Development Kit for the SOAP-based API for Green Button Go Remote Control"
5
+ authors = [
6
+ {name = "Eli Fine"},
7
+
8
+ ]
9
+ readme = "README.md"
10
+ requires-python = ">=3.12.7"
11
+ classifiers = [
12
+ "Programming Language :: Python :: 3",
13
+
14
+ "Programming Language :: Python :: 3.12",
15
+
16
+ "Programming Language :: Python :: 3.13",
17
+
18
+ ]
19
+ dependencies = [
20
+ "zeep>=4.3.2",
21
+ "pydantic>=2.11.7",
22
+ "requests>=2.32.5",
23
+ ]
24
+
25
+
26
+ [dependency-groups]
27
+ dev = [
28
+ # Specific to this repository
29
+ "vcrpy>=7.0.0",
30
+ "pytest-recording>=0.13.4",
31
+
32
+ # Managed by upstream template
33
+ "pyright>=1.1.405",
34
+ "pytest>=8.4.2",
35
+ "pytest-cov>=7.0.0",
36
+ "pytest-randomly>=4.0.1",
37
+
38
+
39
+ ]
40
+
41
+ [tool.setuptools]
42
+ license-files = [] # kludge until this bug is fixed https://github.com/pypa/setuptools/issues/4759
43
+
44
+ [tool.uv]
45
+ package = true
46
+
47
+ [[tool.uv.index]]
48
+ name = "pypi"
49
+ url = "https://pypi.org/simple/"
50
+ publish-url = "https://upload.pypi.org/legacy/"
51
+
52
+ [[tool.uv.index]]
53
+ name = "testpypi"
54
+ url = "https://test.pypi.org/simple/"
55
+ publish-url = "https://test.pypi.org/legacy/"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,23 @@
1
+ from .constants import INSTRUMENT_STATUS_VARIABLE_NAME_SUFFIX
2
+ from .constants import InstrumentName
3
+ from .constants import InstrumentStatus
4
+ from .constants import OrderStatus
5
+ from .constants import ProgramStatus
6
+ from .exceptions import GbgVariableNotFoundError
7
+ from .exceptions import NoGbgProgramLoadedError
8
+ from .exceptions import WorkcellProcessNotAvailableError
9
+ from .models import WorkcellProcessParameter
10
+ from .sdk import GbgRemoteSdk
11
+
12
+ __all__ = [
13
+ "INSTRUMENT_STATUS_VARIABLE_NAME_SUFFIX",
14
+ "GbgRemoteSdk",
15
+ "GbgVariableNotFoundError",
16
+ "InstrumentName",
17
+ "InstrumentStatus",
18
+ "NoGbgProgramLoadedError",
19
+ "OrderStatus",
20
+ "ProgramStatus",
21
+ "WorkcellProcessNotAvailableError",
22
+ "WorkcellProcessParameter",
23
+ ]
@@ -0,0 +1,28 @@
1
+ from enum import Enum
2
+
3
+ INSTRUMENT_STATUS_VARIABLE_NAME_SUFFIX = ".Status"
4
+ type InstrumentName = str
5
+
6
+
7
+ class ProgramStatus(str, Enum):
8
+ IDLE = "Idle"
9
+ RUNNING = "Running"
10
+ PAUSED = "Paused"
11
+ ERROR = "Error"
12
+
13
+
14
+ class InstrumentStatus(str, Enum):
15
+ READY = "Ready"
16
+ BUSY = "Busy"
17
+ ERROR = "Error"
18
+ LOCKED = "Locked"
19
+ UNKNOWN = "Unknown"
20
+
21
+
22
+ class OrderStatus(str, Enum):
23
+ UNKNOWN = "Unkown" # yes, this is really misspelled in the API response # spellchecker:disable-line
24
+ CANCELED = "Canceled"
25
+ COMPLETE = "Complete"
26
+ RUNNING = "Running"
27
+ SCHEDULED = "Scheduled" # TODO: explicitly test this---but it was observed in the wild
28
+ ERROR = "Error" # guessing that this is possible in a fixed version of GBG, but not tested yet
@@ -0,0 +1,19 @@
1
+ class GbgRemoteApiError(Exception):
2
+ pass
3
+
4
+
5
+ class NoGbgProgramLoadedError(GbgRemoteApiError):
6
+ def __init__(self, *, attempted_action: str):
7
+ super().__init__(f"No GBG program is loaded. Attempted action cannot be completed: {attempted_action}")
8
+
9
+
10
+ class WorkcellProcessNotAvailableError(GbgRemoteApiError):
11
+ def __init__(self, *, process_name: str, available_process_names: list[str]):
12
+ super().__init__(
13
+ f"Workcell process not available: {process_name}\nAvailable processes: {', '.join(available_process_names)}"
14
+ )
15
+
16
+
17
+ class GbgVariableNotFoundError(GbgRemoteApiError):
18
+ def __init__(self, variable_name: str):
19
+ super().__init__(f"Variable not found: {variable_name}")
@@ -0,0 +1,18 @@
1
+ from pydantic import BaseModel
2
+
3
+
4
+ class WorkcellProcessParameter(BaseModel):
5
+ variable_name: str
6
+ variable_value: bool | int | float | str | None
7
+
8
+ @property
9
+ def variable_value_in_gbg_format(self) -> str:
10
+ """Convert the variable value to a string that GBG can understand."""
11
+ if self.variable_value is None:
12
+ return ""
13
+ if isinstance(self.variable_value, bool):
14
+ return "True" if self.variable_value else "False"
15
+ if isinstance(self.variable_value, (float, int)):
16
+ if int(self.variable_value) == self.variable_value:
17
+ return str(int(self.variable_value))
18
+ return str(self.variable_value)
File without changes
@@ -0,0 +1,199 @@
1
+ import io
2
+ import logging
3
+ from collections.abc import Callable
4
+ from functools import partial
5
+ from ipaddress import IPv4Address
6
+ from pathlib import Path
7
+ from time import perf_counter_ns
8
+ from typing import Any
9
+ from typing import cast
10
+
11
+ import requests
12
+ from zeep import Client
13
+ from zeep.exceptions import Fault
14
+ from zeep.transports import Transport
15
+
16
+ from .constants import INSTRUMENT_STATUS_VARIABLE_NAME_SUFFIX
17
+ from .constants import InstrumentName
18
+ from .constants import InstrumentStatus
19
+ from .constants import OrderStatus
20
+ from .constants import ProgramStatus
21
+ from .exceptions import GbgVariableNotFoundError
22
+ from .exceptions import NoGbgProgramLoadedError
23
+ from .exceptions import WorkcellProcessNotAvailableError
24
+ from .models import WorkcellProcessParameter
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+
29
+ class GbgRemoteSdk:
30
+ def __init__(self, *, hostname: str | IPv4Address = "localhost", port: int = 8080, route: str = "/GBGRemote"):
31
+ super().__init__()
32
+ path_to_wsdl = Path(__file__).parent / "vendor_files" / "wsdl.xml"
33
+ # TODO: obtain the WSDL from the API endpoint and compare it to the local copy to ensure it matches expectations
34
+ with path_to_wsdl.open("r", encoding="utf-8") as f:
35
+ original_wsdl = f.read()
36
+ endpoint = f"http://{hostname!s}:{port}{route}"
37
+ wsdl_with_desired_endpoint = original_wsdl.replace("http://localhost:8080/GBGRemote", endpoint)
38
+ self._client = Client(
39
+ io.StringIO(wsdl_with_desired_endpoint),
40
+ transport=Transport(
41
+ timeout=0.5, # pyright: ignore[reportArgumentType] # zeep says it only wants ints, but it's just passing it to requests, which accepts floats
42
+ operation_timeout=3,
43
+ ),
44
+ )
45
+ self._short_timeout_client = Client(
46
+ self._client.wsdl,
47
+ transport=Transport(
48
+ timeout=0.5, # pyright: ignore[reportArgumentType] # zeep says it only wants ints, but it's just passing it to requests, which accepts floats
49
+ operation_timeout=0.5,
50
+ ),
51
+ )
52
+ self.hostname = str(hostname) # tried converting everything to Ipv4address, but it didn't work with localhost
53
+ self.port = port
54
+ self.route = route
55
+
56
+ # Known problems: GetStatus will return "Running" even when an error is thrown (later version of GBG fixes this)
57
+ # GetStatusDetails just appears to return an empty string (although maybe if there was an error, it would return something more meaningful)
58
+ # There's no way to specifically get a list of instruments in the workcell---the workaround is just check all variable names for things that end in `.status`
59
+ # There's no known way to tell if GBG is simulating an instrument within a workcell
60
+ def _invoke_request(self, request: Callable[[], Any]) -> Any: # noqa: ANN401 # yes, Any is generally bad, but zeep WSDL is terribly typed
61
+ start_time = perf_counter_ns()
62
+ response = request()
63
+ end_time = perf_counter_ns()
64
+ logger.debug( # TODO: figure out how to include the request name and parameters in the log
65
+ "Invoked GBG Remote API method ", extra={"gbg_remote_api_call_duration_ns": end_time - start_time}
66
+ )
67
+ return response
68
+
69
+ def is_simulated(self) -> bool:
70
+ try:
71
+ response = self._invoke_request(self._client.service.GetIsSimulated)
72
+ except Fault as e:
73
+ if "Object reference not set to an instance of an object" in str(e):
74
+ raise NoGbgProgramLoadedError(attempted_action="GetIsSimulated") from e
75
+ raise # pragma: no cover # not worth triggering a different type of error just to hit this default re-raise
76
+ response = cast(bool, response)
77
+ assert isinstance(response, bool), (
78
+ f"Expected response to be of type bool, but got type {type(response)} for {response}"
79
+ )
80
+ return response
81
+
82
+ def get_loaded_program(self) -> str | None:
83
+ response = cast(str, self._invoke_request(self._client.service.GetLoadedProgram))
84
+ assert isinstance(response, str), (
85
+ f"Expected response to be of type str, but got type {type(response)} for {response}"
86
+ )
87
+ if response == "None":
88
+ return None
89
+ return response
90
+
91
+ def is_api_reachable(self) -> bool:
92
+ try:
93
+ self._invoke_request(
94
+ self._short_timeout_client.service.GetLoadedProgram
95
+ ) # arbitrary API call that should always succeed
96
+ except requests.exceptions.ConnectionError:
97
+ return False
98
+ return True
99
+
100
+ def get_status(self) -> ProgramStatus:
101
+ response = cast(str, self._invoke_request(self._client.service.GetStatus))
102
+ assert isinstance(response, str), (
103
+ f"Expected response to be of type str, but got type {type(response)} for {response}"
104
+ )
105
+ return ProgramStatus(response)
106
+
107
+ def get_variable_names(self) -> list[str]:
108
+ response = self._invoke_request(self._client.service.GetVariableNames)
109
+ if (
110
+ response is None
111
+ ): # there should never actually be no variables...this is what happens when no program is loaded
112
+ raise NoGbgProgramLoadedError(attempted_action="GetVariableNames")
113
+ response = cast(list[str], response)
114
+ assert isinstance(response, list), (
115
+ f"Expected response to be of type list, but got type {type(response)} for {response}"
116
+ )
117
+ return response
118
+
119
+ def get_variable_value(self, variable_name: str) -> str | None:
120
+ try:
121
+ response = self._invoke_request(partial(self._client.service.GetVariableValue, variable_name)) # pyright: ignore[reportUnknownArgumentType] # the WSDL is not typed
122
+ except Fault as e:
123
+ if "Get Variable Value could not find a variable named" in str(e):
124
+ raise GbgVariableNotFoundError(variable_name) from e
125
+ raise # pragma: no cover # not worth triggering a different type of error just to hit this default re-raise
126
+ if response is None:
127
+ return None
128
+ response = cast(str, response)
129
+ assert isinstance(response, str), (
130
+ f"Expected response to be of type str, but got type {type(response)} for {response}"
131
+ )
132
+
133
+ return response
134
+
135
+ def get_instrument_statuses(self) -> dict[InstrumentName, InstrumentStatus]:
136
+ statuses: dict[InstrumentName, InstrumentStatus] = {}
137
+ try:
138
+ variable_names = self.get_variable_names()
139
+ except NoGbgProgramLoadedError as e:
140
+ raise NoGbgProgramLoadedError(attempted_action="GetInstrumentStatuses") from e
141
+ for variable_name in variable_names:
142
+ if variable_name.endswith(INSTRUMENT_STATUS_VARIABLE_NAME_SUFFIX):
143
+ instrument_name = variable_name[: -len(INSTRUMENT_STATUS_VARIABLE_NAME_SUFFIX)]
144
+ status = self.get_variable_value(variable_name)
145
+ statuses[instrument_name] = InstrumentStatus.UNKNOWN if status is None else InstrumentStatus(status)
146
+ return statuses
147
+
148
+ def start_workcell_process(
149
+ self, *, process_name: str, order_id: str, parameters: list[WorkcellProcessParameter]
150
+ ) -> str:
151
+ """Start a workcell process and return the RunID that was started."""
152
+ available_processes = self.get_available_workcell_processes()
153
+ if process_name not in available_processes:
154
+ raise WorkcellProcessNotAvailableError(
155
+ process_name=process_name, available_process_names=available_processes
156
+ )
157
+
158
+ # many things were attempted to use native python data structures of list/dict, but nothing worked except this
159
+ array_type = self._client.get_type( # pyright: ignore[reportUnknownMemberType] # zeep WSDL is not typed
160
+ "{http://schemas.microsoft.com/2003/10/Serialization/Arrays}ArrayOfKeyValueOfstringstring"
161
+ )
162
+ assert array_type is not None, "Failed to get the ArrayOfKeyValueOfstringstring type from the WSDL"
163
+
164
+ parsed_parameters = array_type(
165
+ KeyValueOfstringstring=[
166
+ {"Key": param.variable_name, "Value": param.variable_value_in_gbg_format} for param in parameters
167
+ ]
168
+ )
169
+
170
+ response = self._invoke_request(
171
+ partial( # pyright: ignore[reportUnknownArgumentType] # the WSDL is not typed
172
+ self._client.service.Run,
173
+ workcellProcessName=process_name,
174
+ orderId=order_id,
175
+ parameters=parsed_parameters,
176
+ )
177
+ )
178
+ assert isinstance(response, str), (
179
+ f"Expected response to be of type str, but got type {type(response)} for {response}"
180
+ )
181
+ return response
182
+
183
+ def get_order_status(self, order_id: str) -> OrderStatus:
184
+ response = self._invoke_request(
185
+ partial( # pyright: ignore[reportUnknownArgumentType] # the WSDL is not typed
186
+ self._client.service.GetOrderStatus, orderId=order_id
187
+ )
188
+ )
189
+ assert isinstance(response, str), (
190
+ f"Expected response to be of type str, but got type {type(response)} for {response}"
191
+ )
192
+ return OrderStatus(response)
193
+
194
+ def get_available_workcell_processes(self) -> list[str]:
195
+ response = self._invoke_request(self._client.service.GetAvailableWorkcellProcesses)
196
+ assert isinstance(response, list), (
197
+ f"Expected response to be of type list, but got type {type(response)} for {response}"
198
+ )
199
+ return cast(list[str], response)
@@ -0,0 +1,46 @@
1
+ Metadata-Version: 2.4
2
+ Name: gbg-remote-sdk
3
+ Version: 0.1.0
4
+ Summary: Software Development Kit for the SOAP-based API for Green Button Go Remote Control
5
+ Author: Eli Fine
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: Programming Language :: Python :: 3.12
8
+ Classifier: Programming Language :: Python :: 3.13
9
+ Requires-Python: >=3.12.7
10
+ Description-Content-Type: text/markdown
11
+ Requires-Dist: zeep>=4.3.2
12
+ Requires-Dist: pydantic>=2.11.7
13
+ Requires-Dist: requests>=2.32.5
14
+
15
+ [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://github.com/pre-commit/pre-commit)
16
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
17
+ [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
18
+ [![Checked with pyright](https://microsoft.github.io/pyright/img/pyright_badge.svg)](https://microsoft.github.io/pyright/)
19
+ [![Actions status](https://github.com/lab-sync/gbg-remote-sdk/actions/workflows/ci.yaml/badge.svg?branch=main)](https://github.com/lab-sync/gbg-remote-sdk/actions)
20
+ [![Open in Dev Containers](https://img.shields.io/static/v1?label=Dev%20Containers&message=Open&color=blue)](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/lab-sync/gbg-remote-sdk)
21
+ [![PyPI Version](https://img.shields.io/pypi/v/gbg-remote-sdk.svg)](https://pypi.org/project/gbg-remote-sdk/)
22
+ [![Downloads](https://pepy.tech/badge/gbg-remote-sdk)](https://pepy.tech/project/gbg-remote-sdk)
23
+ [![Python Versions](https://img.shields.io/pypi/pyversions/gbg-remote-sdk.svg)](https://pypi.org/project/gbg-remote-sdk/)
24
+ [![Codecov](https://codecov.io/gh/lab-sync/gbg-remote-sdk/branch/main/graph/badge.svg)](https://codecov.io/gh/lab-sync/gbg-remote-sdk)
25
+
26
+ # Usage
27
+ Documentation is hosted on [ReadTheDocs](https://gbg-remote-sdk.readthedocs.io/en/latest/?badge=latest).
28
+
29
+ # Development
30
+ This project has a dev container. If you already have VS Code and Docker installed, you can click the badge above or [here](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/lab-sync/gbg-remote-sdk) to get started. Clicking these links will cause VS Code to automatically install the Dev Containers extension if needed, clone the source code into a container volume, and spin up a dev container for use.
31
+
32
+ To publish a new version of the repository, you can run the `Publish` workflow manually and publish to the staging registry from any branch, and you can check the 'Publish to Primary' option when on `main` to publish to the primary registry and create a git tag.
33
+
34
+ ### For doing testing of the GBG Remote interface:
35
+ Open a connection to the EC2 instance hosting GBG in AWS:
36
+ ```bash
37
+ aws ssm start-session --target i-033eef3ccfbe82829 --document-name AWS-StartPortForwardingSession --parameters '{"portNumber":["8080"],"localPortNumber":["8080"]}'
38
+ ```
39
+
40
+ ### When running the unit test suite, `pytest-recording` library is used. If it gives you errors about a cassette missing, the parameter to invoke a live HTTP request is `--record-mode=once` (add on to the end of the `pytest` command)
41
+
42
+
43
+
44
+ ## Updating from the template
45
+ This repository uses a copier template. To pull in the latest updates from the template, use the command:
46
+ `copier update --trust --conflict rej --defaults`
@@ -0,0 +1,13 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/gbg_remote_sdk/__init__.py
4
+ src/gbg_remote_sdk/constants.py
5
+ src/gbg_remote_sdk/exceptions.py
6
+ src/gbg_remote_sdk/models.py
7
+ src/gbg_remote_sdk/py.typed
8
+ src/gbg_remote_sdk/sdk.py
9
+ src/gbg_remote_sdk.egg-info/PKG-INFO
10
+ src/gbg_remote_sdk.egg-info/SOURCES.txt
11
+ src/gbg_remote_sdk.egg-info/dependency_links.txt
12
+ src/gbg_remote_sdk.egg-info/requires.txt
13
+ src/gbg_remote_sdk.egg-info/top_level.txt
@@ -0,0 +1,3 @@
1
+ zeep>=4.3.2
2
+ pydantic>=2.11.7
3
+ requests>=2.32.5
@@ -0,0 +1 @@
1
+ gbg_remote_sdk