Skip to content
This repository has been archived by the owner on Jan 21, 2025. It is now read-only.

Commit

Permalink
Merge pull request #12 from wandelbotsgmbh/feature/oauth
Browse files Browse the repository at this point in the history
Feature/oauth
  • Loading branch information
andiikaa authored Nov 6, 2024
2 parents 1ec3ea5 + ee944a3 commit a3d63d6
Show file tree
Hide file tree
Showing 17 changed files with 539 additions and 428 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
poetry install
mkdir envs
touch envs/.env.tests
echo "WANDELAPI_BASE_URL=test.instance.wandelbots.io" > envs/.env.tests
echo "WANDELAPI_BASE_URL=test.instance.mock.io" > envs/.env.tests
echo "CELL_ID=cell" >> envs/.env.tests
echo "MOTION_GROUP=0@virtual-robot" >> envs/.env.tests
echo "TCP=Flange" >> envs/.env.tests
Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,16 @@ For further examples take a look at the [examples](https://github.com/wandelbots
```bash
$ poetry run pytest -rs -v
```

#### integration tests

By default integration tests will be skipped.
To run them localy create an env file at `envs/.env.tests` with your values.

```txt
WANDELAPI_BASE_URL=
NOVA_ACCESS_TOKEN=
CELL_ID=
MOTION_GROUP=
TCP=
```
1 change: 1 addition & 0 deletions examples/.env
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
WANDELAPI_BASE_URL=
NOVA_USERNAME=
NOVA_PASSWORD=
NOVA_ACCESS_TOKEN=
CELL_ID=
MOTION_GROUP=
TCP=
3 changes: 1 addition & 2 deletions examples/01_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@

my_instance = Instance(
url=os.getenv("WANDELAPI_BASE_URL"),
user=os.getenv("NOVA_USERNAME"),
password=os.getenv("NOVA_PASSWORD"),
access_token=os.getenv("NOVA_ACCESS_TOKEN"),
)

my_robot = MotionGroup(
Expand Down
3 changes: 1 addition & 2 deletions examples/02_plan_and_execute.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@

my_instance = Instance(
url=os.getenv("WANDELAPI_BASE_URL"),
user=os.getenv("NOVA_USERNAME"),
password=os.getenv("NOVA_PASSWORD"),
access_token=os.getenv("NOVA_ACCESS_TOKEN"),
)

my_robot = MotionGroup(
Expand Down
3 changes: 1 addition & 2 deletions examples/03_plan_and_execute_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@

my_instance = Instance(
url=os.getenv("WANDELAPI_BASE_URL"),
user=os.getenv("NOVA_USERNAME"),
password=os.getenv("NOVA_PASSWORD"),
access_token=os.getenv("NOVA_ACCESS_TOKEN"),
)

my_robot = MotionGroup(
Expand Down
3 changes: 1 addition & 2 deletions examples/04_execute_in_background.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@

my_instance = Instance(
url=os.getenv("WANDELAPI_BASE_URL"),
user=os.getenv("NOVA_USERNAME"),
password=os.getenv("NOVA_PASSWORD"),
access_token=os.getenv("NOVA_ACCESS_TOKEN"),
)

my_robot = MotionGroup(
Expand Down
12 changes: 5 additions & 7 deletions examples/05_notebook.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
},
{
"cell_type": "code",
"execution_count": 14,
"execution_count": null,
"id": "a9eb2f8c-2527-4ea6-bc1f-8de808a88ad8",
"metadata": {},
"outputs": [],
Expand All @@ -36,8 +36,7 @@
"outputs": [],
"source": [
"os.environ[\"WANDELAPI_BASE_URL\"] = \"https://instance.wandelbots.io\"\n",
"os.environ[\"NOVA_USERNAME\"] = \"wb\"\n",
"os.environ[\"NOVA_PASSWORD\"] = \"your_password\"\n",
"os.environ[\"NOVA_ACCESS_TOKEN\"] = \"\"\n",
"os.environ[\"CELL_ID\"] = \"cell\""
]
},
Expand Down Expand Up @@ -70,15 +69,14 @@
},
{
"cell_type": "code",
"execution_count": 9,
"execution_count": null,
"id": "73548c34-9434-4043-b6a9-51ac98fac701",
"metadata": {},
"outputs": [],
"source": [
"my_instance = Instance(\n",
" url=os.getenv(\"WANDELAPI_BASE_URL\"),\n",
" user=os.getenv(\"NOVA_USERNAME\"),\n",
" password=os.getenv(\"NOVA_PASSWORD\"),\n",
" access_token=os.getenv(\"NOVA_ACCESS_TOKEN\"),\n",
")\n",
"\n",
"motion_groups = motion_group_api.get_active_motion_groups(instance=my_instance, cell=os.getenv(\"CELL_ID\"))\n",
Expand Down Expand Up @@ -202,7 +200,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.4"
"version": "3.9.19"
}
},
"nbformat": 4,
Expand Down
791 changes: 406 additions & 385 deletions poetry.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ readme = "README.md"
python = "^3.9"
httpx = "0.27.2"
requests = "2.32.3"
wandelbots_api_client = "24.5.0"
wandelbots_api_client = "24.8.0.dev17"
websockets = "13.0.1"
numpy = "^1.26"
scipy = "^1.11"
Expand Down
32 changes: 25 additions & 7 deletions tests/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@
import requests
import os
from dotenv import load_dotenv, find_dotenv
import requests.auth


def get_auth_token() -> dict[str, str]:
token = os.getenv("NOVA_ACCESS_TOKEN")
if not token:
return None
return {"Authorization": f"Bearer {token}"}


def get_basic_auth() -> requests.auth.HTTPBasicAuth:
username = os.getenv("NOVA_USERNAME")
password = os.getenv("NOVA_PASSWORD")
if not username or not password:
return None
return requests.auth.HTTPBasicAuth(username, password)


@pytest.fixture(scope="session", autouse=True)
Expand All @@ -18,13 +34,15 @@ def check_test_motion_group_available(request):

# General availability check
try:
if not os.getenv("NOVA_USERNAME") or not os.getenv("NOVA_PASSWORD"):
auth = None
else:
auth = requests.auth.HTTPBasicAuth(
os.getenv("NOVA_USERNAME"), os.getenv("NOVA_PASSWORD")
headers = get_auth_token()
auth = get_basic_auth()

if "wandelbots.io" in nova_host and not headers and not auth:
pytest.fail(
"Please provide NOVA_ACCESS_TOKEN or NOVA_USERNAME and NOVA_PASSWORD in the environment (depending on the used auth method)."
)
response = requests.get(nova_host, timeout=5, auth=auth)

response = requests.get(nova_host, timeout=5, headers=headers, auth=auth)
response.raise_for_status()
except requests.RequestException as e:
skip_reason = f"Skipping tests: Backend service is not available at {nova_host}. Error: {e}"
Expand All @@ -44,7 +62,7 @@ def check_test_motion_group_available(request):
f"{nova_host}/api/v1/cells/{cell}/motion-groups/{motion_group}/state?tcp={tcp}"
)
try:
response = requests.get(endpoint_url, timeout=5, auth=auth)
response = requests.get(endpoint_url, timeout=5, headers=headers, auth=auth)
response.raise_for_status()
except requests.HTTPError as e:
try:
Expand Down
1 change: 1 addition & 0 deletions tests/integration/test_01_robot_initalization.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ async def test_motion_group_initialization():
"""Test motion-group initialization and retrieve basic state from the actual backend."""
instance = Instance(
url=os.getenv("WANDELAPI_BASE_URL"),
access_token=os.getenv("NOVA_ACCESS_TOKEN"),
user=os.getenv("NOVA_USERNAME"),
password=os.getenv("NOVA_PASSWORD"),
)
Expand Down
1 change: 1 addition & 0 deletions tests/integration/test_02_planning_and_execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ async def test_motion_planning_and_execution():

instance = Instance(
url=os.getenv("WANDELAPI_BASE_URL"),
access_token=os.getenv("NOVA_ACCESS_TOKEN"),
user=os.getenv("NOVA_USERNAME"),
password=os.getenv("NOVA_PASSWORD"),
)
Expand Down
5 changes: 4 additions & 1 deletion wandelbots/apis/motion.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,10 @@ async def plan_motion_async(
def _get_wb_api_client(instance: Instance) -> wb_api.ApiClient:
_url = f"{instance.url}/api/v1"
_conf = wb_api.Configuration(
host=_url, username=instance.user, password=instance.password
host=_url,
username=instance.user,
password=instance.password,
access_token=instance.access_token,
)
return wb_api.ApiClient(_conf)

Expand Down
28 changes: 22 additions & 6 deletions wandelbots/core/instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ def __init__(
url="http://api-gateway.wandelbots.svc.cluster.local:8080",
user=None,
password=None,
access_token=None,
):
self._api_version = "v1"
self.access_token = access_token
self.user = user
self.password = password
self.url = self._parse_url(url)
Expand All @@ -17,13 +19,21 @@ def __init__(
def _parse_url(self, host: str) -> str:
"""remove any trailing slashes and validate scheme"""
_url = host.rstrip("/")

if self.has_access_token() and self.has_basic_auth():
raise ValueError(
"please choose either user and password or access token access"
)

if _url.startswith("https"):
if not self.user or not self.password:
raise ValueError("User and password are required for https connections")
if not self.has_auth():
raise ValueError(
"Access token or user and password are required for https connections"
)
elif _url.startswith("http"):
if self.user or self.password:
if self.has_auth():
raise ValueError(
"User and password are not required for http connections"
"Access token and/or user and password are not required for http connections"
)
elif "wandelbots.io" in _url:
_url = "https://" + _url
Expand All @@ -38,12 +48,18 @@ def socket_uri(self):
return f"{_uri}/api/{self._api_version}"
else:
_url_no_scheme = self.url.split("://")[1]
_uri = f"wss://{self.user}:{self.password}@{_url_no_scheme}"
_uri = f"wss://{_url_no_scheme}"
return f"{_uri}/api/{self._api_version}"

def _connect(self):
self.logger.info(f"Connecting to {self.url}")
# do some connection stuff

def has_auth(self):
return self.user and self.password
return self.has_basic_auth() or self.has_access_token()

def has_basic_auth(self):
return self.user is not None and self.password is not None

def has_access_token(self):
return self.access_token is not None
30 changes: 24 additions & 6 deletions wandelbots/request/asyncs.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
import httpx
from typing import Dict, Tuple, Optional

import requests
from wandelbots.util.logger import _get_logger
from wandelbots.request.config import TIMEOUT
from wandelbots.core.instance import Instance

__logger = _get_logger(__name__)


def _get_auth_header(instance: Instance) -> Optional[Dict[str, str]]:
if instance.has_access_token():
return {"Authorization": f"Bearer {instance.access_token}"}
return None


def _get_auth(instance: Instance) -> Optional[httpx.BasicAuth]:
if instance.has_auth():
return httpx.BasicAuth(username=instance.user, password=instance.password)
if instance.has_basic_auth():
return requests.auth.HTTPBasicAuth(
username=instance.user, password=instance.password
)
return None


Expand All @@ -28,7 +38,9 @@ def _handle_request_error(err):


async def get(url: str, instance: Instance) -> Tuple[int, Optional[Dict]]:
async with httpx.AsyncClient(auth=_get_auth(instance)) as client:
async with httpx.AsyncClient(
headers=_get_auth_header(instance), auth=_get_auth(instance)
) as client:
try:
response = await client.get(url, timeout=TIMEOUT)
response.raise_for_status()
Expand All @@ -39,7 +51,9 @@ async def get(url: str, instance: Instance) -> Tuple[int, Optional[Dict]]:


async def delete(url: str, instance: Instance) -> int:
async with httpx.AsyncClient(auth=_get_auth(instance)) as client:
async with httpx.AsyncClient(
headers=_get_auth_header(instance), auth=_get_auth(instance)
) as client:
try:
response = await client.delete(url, timeout=TIMEOUT)
response.raise_for_status()
Expand All @@ -52,7 +66,9 @@ async def delete(url: str, instance: Instance) -> int:
async def post(
url: str, instance: Instance, data: Dict = {}
) -> Tuple[int, Optional[Dict]]:
async with httpx.AsyncClient(auth=_get_auth(instance)) as client:
async with httpx.AsyncClient(
headers=_get_auth_header(instance), auth=_get_auth(instance)
) as client:
try:
response = await client.post(url, json=data, timeout=TIMEOUT)
response.raise_for_status()
Expand All @@ -65,7 +81,9 @@ async def post(
async def put(
url: str, instance: Instance, data: Dict = {}
) -> Tuple[int, Optional[Dict]]:
async with httpx.AsyncClient(auth=_get_auth(instance)) as client:
async with httpx.AsyncClient(
headers=_get_auth_header(instance), auth=_get_auth(instance)
) as client:
try:
response = await client.put(url, json=data, timeout=TIMEOUT)
response.raise_for_status()
Expand Down
Loading

0 comments on commit a3d63d6

Please sign in to comment.