Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3836,6 +3836,7 @@ Example of two training jobs.
"tags": [],
"contentCount": 23,
"userName": "Admin",
"userEmail": "admin@example.com",
"createdAt": "2023-10-31T07:10:28.306Z",
"completedAt": null,
"customModel": {
Expand All @@ -3862,6 +3863,7 @@ Example of two training jobs.
],
"contentCount": 20,
"userName": "Admin",
"userEmail": "admin@example.com",
"createdAt": "2023-10-31T06:56:28.112Z",
"completedAt": "2023-10-31T07:08:26.000Z",
"customModel": {
Expand Down Expand Up @@ -4134,6 +4136,116 @@ client.create_model_monitoring_request_results(
)
```

## Workspace User

### Get workspace users

Returns a list of internal workspace users. (Up to 20 at a time by default)
Each user includes its granted module permissions in `functionResourcePermissions`.

```python
import fastlabel
client = fastlabel.Client()

users = client.get_workspace_users(
keyword="", # Search keyword for name or email (Optional)
offset=0, # The starting position number to fetch (Optional)
limit=20, # The max number to fetch (Optional, default 20)
)
# [
# {
# "id": "...",
# "userId": "...",
# "userSlug": "...",
# "userName": "John Doe",
# "userEmail": "john@example.com",
# "role": "member",
# "isExternal": False,
# "createdAt": "...",
# "updatedAt": "...",
# "functionResourcePermissions": {
# "annotation": True,
# "modelDev": False,
# "dataset": False
# }
# }
# ]
```

### Create workspace user

Creates an internal workspace user. The `slug` is generated automatically on the server side.
Module permissions are managed separately (see below).

```python
user = client.create_workspace_user(
name="John Doe",
email="john@example.com",
language="en", # 'en' or 'ja'
role="member", # 'member' or 'owner'
)
```

### Update workspace user

Updates an internal workspace user. The user is identified by `email` and only
the `role` can be changed. Passing `role="none"` removes the user from the
workspace (equivalent to `delete_workspace_user`).

```python
user = client.update_workspace_user(
email="john@example.com",
role="owner", # 'member', 'owner' or 'none'
)
```

### Delete workspace user

Removes an internal workspace user from the workspace. There is no dedicated
delete endpoint; this updates the user's role to `none`.

```python
client.delete_workspace_user(email="john@example.com")
```

### Grant module permissions

Grants module permissions to an internal workspace user.
`modules` accepts a single module or a list (each is sent as a separate request).

```python
# Single module
client.create_workspace_user_module_permissions(
email="john@example.com",
modules="annotation", # 'annotation', 'modelDev' or 'dataset'
)

# Multiple modules
client.create_workspace_user_module_permissions(
email="john@example.com",
modules=["annotation", "dataset"],
)
```

### Revoke module permissions

Revokes module permissions from an internal workspace user.
`modules` accepts a single module or a list (each is sent as a separate request).

```python
# Single module
client.delete_workspace_user_module_permissions(
email="john@example.com",
modules="annotation", # 'annotation', 'modelDev' or 'dataset'
)

# Multiple modules
client.delete_workspace_user_module_permissions(
email="john@example.com",
modules=["annotation", "dataset"],
)
```

## API Docs

Check [this](https://api.fastlabel.ai/docs/) for further information.
132 changes: 132 additions & 0 deletions fastlabel/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5471,6 +5471,138 @@ def get_project_comments(
params["limit"] = limit
return self.api.get_request(endpoint, params=params)

def get_workspace_users(
self,
keyword: str = None,
offset: int = None,
limit: int = 20,
) -> list:
"""
Returns a list of internal workspace users.
keyword is a search keyword for name or email (Optional).
offset is the starting position number to fetch (Optional).
limit is the max number to fetch (Optional, default 20).
"""
endpoint = "workspaces-users"
params = {}
if keyword:
params["keyword"] = keyword
if offset is not None:
params["offset"] = offset
if limit is not None:
params["limit"] = limit
return self.api.get_request(endpoint, params=params)

def create_workspace_user(
self,
name: str,
email: str,
language: str,
role: str,
) -> dict:
"""
Creates an internal workspace user and returns the created user.
name is the user's name (Required).
email is the user's email address (Required).
language is the user's language, 'en' or 'ja' (Required).
role is the workspace role, 'member' or 'owner' (Required).
Module permissions are managed separately; use
create_workspace_user_module_permission to grant them.
"""
endpoint = "workspaces-users/internal-users"
payload = {
"name": name,
"email": email,
"language": language,
"role": role,
}
return self.api.post_request(endpoint, payload=payload)

def update_workspace_user(
self,
email: str,
role: str,
) -> dict:
"""
Updates an internal workspace user and returns the updated user.
The user is identified by email. Only the role can be changed.
Passing role='none' removes the user from the workspace
(equivalent to delete_workspace_user).
email is the email address of the workspace user (Required).
role is the workspace role, 'member', 'owner' or 'none' (Required).
"""
endpoint = "workspaces-users/internal-users"
payload = {"email": email, "role": role}
return self.api.put_request(endpoint, payload=payload)

def delete_workspace_user(self, email: str) -> None:
"""
Removes an internal workspace user from the workspace.
There is no dedicated delete endpoint; this is done by updating the
user's role to 'none'.
email is the email address of the workspace user (Required).
"""
endpoint = "workspaces-users/internal-users"
self.api.put_request(endpoint, payload={"email": email, "role": "none"})

def create_workspace_user_module_permissions(
self,
email: str,
modules: Union[str, List[str]],
) -> List[str]:
"""
Grants module permissions to an internal workspace user.
Each module is granted with a separate request; if one fails (e.g. the
module user limit is reached), the permissions granted before it remain.
email is the email address of the workspace user (Required).
modules is a single module or a list of modules, each one of
'annotation', 'modelDev', 'dataset' (Required).
"""
if isinstance(modules, str):
modules = [modules]
module_paths = {
"annotation": "annotation",
"dataset": "dataset",
"modelDev": "model-dev",
}
results = []
for module in modules:
if module not in module_paths:
raise FastLabelInvalidException(
"module must be one of 'annotation', 'modelDev', 'dataset'.", 422
)
endpoint = (
f"function-resource-permissions/{module_paths[module]}/internal-users"
)
results.append(self.api.post_request(endpoint, payload={"email": email}))
return results

def delete_workspace_user_module_permissions(
self,
email: str,
modules: Union[str, List[str]],
) -> None:
"""
Revokes module permissions from an internal workspace user.
Each module is revoked with a separate request; if one fails, the
permissions revoked before it remain revoked.
email is the email address of the workspace user (Required).
modules is a single module or a list of modules, each one of
'annotation', 'modelDev', 'dataset' (Required).
"""
if isinstance(modules, str):
modules = [modules]
endpoint = "function-resource-permissions"
for module in modules:
if module not in ("annotation", "modelDev", "dataset"):
raise FastLabelInvalidException(
"module must be one of 'annotation', 'modelDev', 'dataset'.", 422
)
self.api.delete_request(
endpoint,
payload={"email": email, "resource": module},
)

def mask_to_fastlabel_segmentation_points(
self, mask_image: Union[str, np.ndarray]
) -> List[List[List[int]]]:
Expand Down
10 changes: 8 additions & 2 deletions fastlabel/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def get_request(self, endpoint: str, params=None) -> Union[dict, list]:
else:
raise FastLabelException(error, r.status_code)

def delete_request(self, endpoint: str, params=None) -> dict:
def delete_request(self, endpoint: str, params=None, payload=None) -> dict:
"""Makes a delete request to an endpoint.
If an error occurs, assumes that endpoint returns JSON as:
{ 'statusCode': XXX,
Expand All @@ -55,7 +55,9 @@ def delete_request(self, endpoint: str, params=None) -> dict:
"Content-Type": "application/json",
"Authorization": self.access_token,
}
r = requests.delete(self.base_url + endpoint, headers=headers, params=params)
r = requests.delete(
self.base_url + endpoint, headers=headers, params=params, json=payload
)

if r.status_code == 200 or r.status_code == 204:
return
Expand Down Expand Up @@ -110,7 +112,11 @@ def put_request(self, endpoint, payload=None):
r = requests.put(self.base_url + endpoint, json=payload, headers=headers)

if r.status_code == 200:
if not r.content:
return
return r.json()
elif r.status_code == 204:
return
else:
try:
error = r.json()["message"]
Expand Down
6 changes: 5 additions & 1 deletion fastlabel/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
class FastLabelException(Exception):
def __init__(self, message, errcode):
def __init__(self, message, errcode=None):
super(FastLabelException, self).__init__(
"<Response [{}]> {}".format(errcode, message)
)
self.message = message
self.code = errcode

def __reduce__(self):
return (self.__class__, (self.message, self.code))


class FastLabelInvalidException(FastLabelException, ValueError):
pass
Loading
Loading