Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6469b6f757 | |||
| b28db0881c | |||
| b0b442de23 | |||
| 32d2fe8ead | |||
| 9fd40eac23 | |||
| b22ba1f1f1 | |||
| 5e20fe7bf1 | |||
| 6da5da23c4 | |||
| 1b59f0b026 | |||
| 78123936d2 | |||
| dbc1aefed3 |
@@ -22,6 +22,6 @@ RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/
|
||||
|
||||
USER vscode
|
||||
|
||||
RUN curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python -
|
||||
RUN curl -sSL https://install.python-poetry.org | python3 -
|
||||
|
||||
ENV PATH=$PATH:$HOME/.poetry/env
|
||||
|
||||
@@ -33,7 +33,7 @@ What is Speckle? Check our ](https://speckle.xyz) ⇒ creating an account at our public server
|
||||
- [](https://app.speckle.systems) ⇒ creating an account at our public server
|
||||
- [](https://marketplace.digitalocean.com/apps/speckle-server?refcode=947a2b5d7dc1) ⇒ deploying an instance in 1 click
|
||||
|
||||
### Resources
|
||||
|
||||
@@ -7,7 +7,8 @@ from specklepy.logging import metrics
|
||||
|
||||
|
||||
class Resource(CoreResource):
|
||||
"""API Access class for users"""
|
||||
"""API Access class for users. This class provides methods to get and update
|
||||
the user profile, fetch user activity, and manage pending stream invitations."""
|
||||
|
||||
def __init__(self, account, basepath, client, server_version) -> None:
|
||||
super().__init__(
|
||||
@@ -19,13 +20,9 @@ class Resource(CoreResource):
|
||||
self.schema = User
|
||||
|
||||
def get(self) -> User:
|
||||
"""Gets the profile of a user. If no id argument is provided,
|
||||
will return the current authenticated user's profile
|
||||
"""Gets the profile of the current authenticated user's profile
|
||||
(as extracted from the authorization header).
|
||||
|
||||
Arguments:
|
||||
id {str} -- the user id
|
||||
|
||||
Returns:
|
||||
User -- the retrieved user
|
||||
"""
|
||||
@@ -41,11 +38,11 @@ class Resource(CoreResource):
|
||||
):
|
||||
"""Updates your user profile. All arguments are optional.
|
||||
|
||||
Arguments:
|
||||
name {str} -- your name
|
||||
company {str} -- the company you may or may not work for
|
||||
bio {str} -- tell us about yourself
|
||||
avatar {str} -- a nice photo of yourself
|
||||
Args:
|
||||
name (Optional[str]): The user's name.
|
||||
company (Optional[str]): The company the user works for.
|
||||
bio (Optional[str]): A brief user biography.
|
||||
avatar (Optional[str]): A URL to an avatar image for the user.
|
||||
|
||||
Returns @deprecated(version=DEPRECATION_VERSION, reason=DEPRECATION_TEXT):
|
||||
bool -- True if your profile was updated successfully
|
||||
@@ -62,35 +59,30 @@ class Resource(CoreResource):
|
||||
cursor: Optional[datetime] = None,
|
||||
):
|
||||
"""
|
||||
Get the activity from a given stream in an Activity collection.
|
||||
Step into the activity `items` for the list of activity.
|
||||
If no id argument is provided, will return the current authenticated user's
|
||||
activity (as extracted from the authorization header).
|
||||
Fetches collection the current authenticated user's activity
|
||||
as filtered by given parameters
|
||||
|
||||
Note: all timestamps arguments should be `datetime` of any tz as they will be
|
||||
converted to UTC ISO format strings
|
||||
|
||||
user_id {str} -- the id of the user to get the activity from
|
||||
action_type {str} -- filter results to a single action type
|
||||
(eg: `commit_create` or `commit_receive`)
|
||||
limit {int} -- max number of Activity items to return
|
||||
before {datetime} -- latest cutoff for activity
|
||||
(ie: return all activity _before_ this time)
|
||||
after {datetime} -- oldest cutoff for activity
|
||||
(ie: return all activity _after_ this time)
|
||||
cursor {datetime} -- timestamp cursor for pagination
|
||||
Args:
|
||||
limit (int): The maximum number of activity items to return.
|
||||
action_type (Optional[str]): Filter results to a single action type.
|
||||
before (Optional[datetime]): Latest cutoff for activity to include.
|
||||
after (Optional[datetime]): Oldest cutoff for an activity to include.
|
||||
cursor (Optional[datetime]): Timestamp cursor for pagination.
|
||||
|
||||
Returns:
|
||||
Activity collection, filtered according to the provided parameters.
|
||||
"""
|
||||
metrics.track(metrics.SDK, self.account, {"name": "User Active Activity"})
|
||||
return super().activity(limit, action_type, before, after, cursor)
|
||||
|
||||
def get_all_pending_invites(self) -> List[PendingStreamCollaborator]:
|
||||
"""Get all of the active user's pending stream invites
|
||||
|
||||
Requires Speckle Server version >= 2.6.4
|
||||
"""Fetches all of the current user's pending stream invitations.
|
||||
|
||||
Returns:
|
||||
List[PendingStreamCollaborator]
|
||||
-- a list of pending invites for the current user
|
||||
List[PendingStreamCollaborator]: A list of pending stream invitations.
|
||||
"""
|
||||
metrics.track(
|
||||
metrics.SDK, self.account, {"name": "User Active Invites All Get"}
|
||||
@@ -100,18 +92,14 @@ class Resource(CoreResource):
|
||||
def get_pending_invite(
|
||||
self, stream_id: str, token: Optional[str] = None
|
||||
) -> Optional[PendingStreamCollaborator]:
|
||||
"""Get a particular pending invite for the active user on a given stream.
|
||||
If no invite_id is provided, any valid invite will be returned.
|
||||
"""Fetches a specific pending invite for the current user on a given stream.
|
||||
|
||||
Requires Speckle Server version >= 2.6.4
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream to look for invites on
|
||||
token {str} -- the token of the invite to look for (optional)
|
||||
Args:
|
||||
stream_id (str): The ID of the stream to look for invites on.
|
||||
token (Optional[str]): The token of the invite to look for (optional).
|
||||
|
||||
Returns:
|
||||
PendingStreamCollaborator
|
||||
-- the invite for the given stream (or None if it isn't found)
|
||||
Optional[PendingStreamCollaborator]: The invite for the given stream, or None if not found.
|
||||
"""
|
||||
metrics.track(metrics.SDK, self.account, {"name": "User Active Invite Get"})
|
||||
return super().get_pending_invite(stream_id, token)
|
||||
|
||||
@@ -8,7 +8,11 @@ from specklepy.logging.exceptions import SpeckleException
|
||||
|
||||
|
||||
class Resource(CoreResource):
|
||||
"""API Access class for other users, that are not the currently active user."""
|
||||
"""
|
||||
Provides API access to other users' profiles and activities on the platform.
|
||||
This class enables fetching limited information about users, searching for users by name or email,
|
||||
and accessing user activity logs with appropriate privacy and access control measures in place.
|
||||
"""
|
||||
|
||||
def __init__(self, account, basepath, client, server_version) -> None:
|
||||
super().__init__(
|
||||
@@ -21,13 +25,13 @@ class Resource(CoreResource):
|
||||
|
||||
def get(self, id: str) -> LimitedUser:
|
||||
"""
|
||||
Gets the profile of another user.
|
||||
Retrieves the profile of a user specified by their user ID.
|
||||
|
||||
Arguments:
|
||||
id {str} -- the user id
|
||||
Args:
|
||||
id (str): The unique identifier of the user.
|
||||
|
||||
Returns:
|
||||
LimitedUser -- the retrieved profile of another user
|
||||
LimitedUser: The profile of the user with limited information.
|
||||
"""
|
||||
metrics.track(metrics.SDK, self.account, {"name": "Other User Get"})
|
||||
return super().get(id)
|
||||
@@ -35,18 +39,21 @@ class Resource(CoreResource):
|
||||
def search(
|
||||
self, search_query: str, limit: int = 25
|
||||
) -> Union[List[LimitedUser], SpeckleException]:
|
||||
"""Searches for user by name or email. The search query must be at least
|
||||
3 characters long
|
||||
"""
|
||||
Searches for users by name or email.
|
||||
The search requires a minimum query length of 3 characters.
|
||||
|
||||
Args:
|
||||
search_query (str): The search string.
|
||||
limit (int): Maximum number of search results to return.
|
||||
|
||||
Arguments:
|
||||
search_query {str} -- a string to search for
|
||||
limit {int} -- the maximum number of results to return
|
||||
Returns:
|
||||
List[LimitedUser] -- a list of User objects that match the search query
|
||||
Union[List[LimitedUser], SpeckleException]: A list of users matching the search
|
||||
query or an exception if the query is too short.
|
||||
"""
|
||||
if len(search_query) < 3:
|
||||
return SpeckleException(
|
||||
message="User search query must be at least 3 characters"
|
||||
message="User search query must be at least 3 characters."
|
||||
)
|
||||
|
||||
metrics.track(metrics.SDK, self.account, {"name": "Other User Search"})
|
||||
@@ -62,21 +69,19 @@ class Resource(CoreResource):
|
||||
cursor: Optional[datetime] = None,
|
||||
) -> ActivityCollection:
|
||||
"""
|
||||
Get the activity from a given stream in an Activity collection.
|
||||
Step into the activity `items` for the list of activity.
|
||||
Retrieves a collection of activities for a specified user, with optional filters for activity type,
|
||||
time frame, and pagination.
|
||||
|
||||
Note: all timestamps arguments should be `datetime` of
|
||||
any tz as they will be converted to UTC ISO format strings
|
||||
Args:
|
||||
user_id (str): The ID of the user whose activities are being requested.
|
||||
limit (int): The maximum number of activity items to return.
|
||||
action_type (Optional[str]): A specific type of activity to filter.
|
||||
before (Optional[datetime]): Latest timestamp to include activities before.
|
||||
after (Optional[datetime]): Earliest timestamp to include activities after.
|
||||
cursor (Optional[datetime]): Timestamp for pagination cursor.
|
||||
|
||||
user_id {str} -- the id of the user to get the activity from
|
||||
action_type {str} -- filter results to a single action type
|
||||
(eg: `commit_create` or `commit_receive`)
|
||||
limit {int} -- max number of Activity items to return
|
||||
before {datetime} -- latest cutoff for activity
|
||||
(ie: return all activity _before_ this time)
|
||||
after {datetime} -- oldest cutoff for activity
|
||||
(ie: return all activity _after_ this time)
|
||||
cursor {datetime} -- timestamp cursor for pagination
|
||||
Returns:
|
||||
ActivityCollection: A collection of user activities filtered according to specified criteria.
|
||||
"""
|
||||
metrics.track(metrics.SDK, self.account, {"name": "Other User Activity"})
|
||||
return super().activity(user_id, limit, action_type, before, after, cursor)
|
||||
|
||||
@@ -123,8 +123,7 @@ class SpeckleClient:
|
||||
Arguments:
|
||||
token {str} -- an api token
|
||||
"""
|
||||
self.authenticate_with_token(token)
|
||||
self._set_up_client()
|
||||
self.authenticate_with_account(get_account_from_token(token))
|
||||
|
||||
def authenticate_with_token(self, token: str) -> None:
|
||||
"""
|
||||
@@ -135,7 +134,7 @@ class SpeckleClient:
|
||||
Arguments:
|
||||
token {str} -- an api token
|
||||
"""
|
||||
self.account = get_account_from_token(token, self.url)
|
||||
self.account = Account.from_token(token, self.url)
|
||||
self._set_up_client()
|
||||
|
||||
def authenticate_with_account(self, account: Account) -> None:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from pydantic import BaseModel, Field # pylint: disable=no-name-in-module
|
||||
|
||||
@@ -142,6 +143,28 @@ def get_account_from_token(token: str, server_url: str = None) -> Account:
|
||||
return Account.from_token(token, server_url)
|
||||
|
||||
|
||||
def get_accounts_for_server(host: str) -> List[Account]:
|
||||
all_accounts = get_local_accounts()
|
||||
filtered: List[Account] = []
|
||||
|
||||
for acc in all_accounts:
|
||||
moved_from = (
|
||||
acc.serverInfo.migration.movedFrom if acc.serverInfo.migration else None
|
||||
)
|
||||
|
||||
if moved_from and host == urlparse(moved_from).netloc:
|
||||
filtered.append(acc)
|
||||
|
||||
for acc in all_accounts:
|
||||
if any([x for x in filtered if x.userInfo.id == acc.userInfo.id]):
|
||||
continue
|
||||
|
||||
if host == urlparse(acc.serverInfo.url).netloc:
|
||||
filtered.append(acc)
|
||||
|
||||
return filtered
|
||||
|
||||
|
||||
class StreamWrapper:
|
||||
def __init__(self, url: str = None) -> None:
|
||||
raise SpeckleException(
|
||||
|
||||
@@ -185,6 +185,11 @@ class ActivityCollection(BaseModel):
|
||||
return self.__repr__()
|
||||
|
||||
|
||||
class ServerMigration(BaseModel):
|
||||
movedTo: Optional[str] = None
|
||||
movedFrom: Optional[str] = None
|
||||
|
||||
|
||||
class ServerInfo(BaseModel):
|
||||
name: Optional[str] = None
|
||||
company: Optional[str] = None
|
||||
@@ -197,3 +202,4 @@ class ServerInfo(BaseModel):
|
||||
authStrategies: Optional[List[dict]] = None
|
||||
version: Optional[str] = None
|
||||
frontend2: Optional[bool] = None
|
||||
migration: Optional[ServerMigration] = None
|
||||
|
||||
@@ -7,7 +7,7 @@ from specklepy.core.api.client import SpeckleClient
|
||||
from specklepy.core.api.credentials import (
|
||||
Account,
|
||||
get_account_from_token,
|
||||
get_local_accounts,
|
||||
get_accounts_for_server,
|
||||
)
|
||||
from specklepy.logging.exceptions import SpeckleException, SpeckleWarning
|
||||
from specklepy.transports.server.server import ServerTransport
|
||||
@@ -178,14 +178,7 @@ class StreamWrapper:
|
||||
if self._account and self._account.token:
|
||||
return self._account
|
||||
|
||||
self._account = next(
|
||||
(
|
||||
a
|
||||
for a in get_local_accounts()
|
||||
if self.host == urlparse(a.serverInfo.url).netloc
|
||||
),
|
||||
None,
|
||||
)
|
||||
self._account = next(iter(get_accounts_for_server(self.host)), None)
|
||||
|
||||
if not self._account:
|
||||
self._account = get_account_from_token(token, self.server_url)
|
||||
|
||||
@@ -303,15 +303,15 @@ class Polyline(Base, speckle_type=GEOMETRY + "Polyline", chunkable={"value": 200
|
||||
|
||||
|
||||
class SpiralType(Enum):
|
||||
Biquadratic = (0,)
|
||||
BiquadraticParabola = (1,)
|
||||
Bloss = (2,)
|
||||
Clothoid = (3,)
|
||||
Cosine = (4,)
|
||||
Cubic = (5,)
|
||||
CubicParabola = (6,)
|
||||
Radioid = (7,)
|
||||
Sinusoid = (8,)
|
||||
Biquadratic = 0
|
||||
BiquadraticParabola = 1
|
||||
Bloss = 2
|
||||
Clothoid = 3
|
||||
Cosine = 4
|
||||
Cubic = 5
|
||||
CubicParabola = 6
|
||||
Radioid = 7
|
||||
Sinusoid = 8
|
||||
Unknown = 9
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import os
|
||||
import uuid
|
||||
from typing import List, Optional, Tuple
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import pytest
|
||||
|
||||
from specklepy.core.api.credentials import Account, UserInfo, get_accounts_for_server
|
||||
from specklepy.core.api.models import ServerInfo, ServerMigration
|
||||
from specklepy.core.helpers import speckle_path_provider
|
||||
|
||||
|
||||
def _create_account(
|
||||
id: str, url: str, movedFrom: Optional[str], movedTo: Optional[str]
|
||||
) -> Account:
|
||||
return Account(
|
||||
id=uuid.uuid4().hex[:6].lower(),
|
||||
token="myToken",
|
||||
serverInfo=ServerInfo(
|
||||
url=url,
|
||||
name="myServer",
|
||||
migration=ServerMigration(movedTo=movedTo, movedFrom=movedFrom),
|
||||
),
|
||||
userInfo=UserInfo(id=id),
|
||||
)
|
||||
|
||||
|
||||
def _test_cases() -> List[Tuple[List[Account], str, List[Account]]]:
|
||||
user_id_1 = uuid.uuid4().hex[:6].lower()
|
||||
user_id_2 = uuid.uuid4().hex[:6].lower()
|
||||
old = _create_account(
|
||||
user_id_1, "https://old.example.com", None, "https://new.example.com"
|
||||
)
|
||||
new = _create_account(
|
||||
user_id_1, "https://new.example.com", "https://old.example.com", None
|
||||
)
|
||||
other = _create_account(user_id_2, "https://other.example.com", None, None)
|
||||
|
||||
given_accounts = [old, new, other]
|
||||
reversed = [other, new, old]
|
||||
|
||||
return [
|
||||
(given_accounts, "https://old.example.com", [new]),
|
||||
(given_accounts, "https://new.example.com", [new]),
|
||||
(reversed, "https://old.example.com", [new]),
|
||||
]
|
||||
|
||||
|
||||
def _clean_accounts(accounts: List[Account]) -> None:
|
||||
json_accounts = speckle_path_provider.accounts_folder_path()
|
||||
|
||||
for acc in accounts:
|
||||
# deleting acc json file in json_accounts path
|
||||
os.remove(os.path.join(json_accounts, f"{acc.id}.json"))
|
||||
pass
|
||||
|
||||
|
||||
def _add_accounts(accounts: List[Account]) -> None:
|
||||
json_accounts = speckle_path_provider.accounts_folder_path()
|
||||
|
||||
for acc in accounts:
|
||||
data = Account.model_dump_json(acc)
|
||||
with open(os.path.join(json_accounts, f"{acc.id}.json"), "w") as f:
|
||||
f.write(data)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("accounts, requested_url, expected", _test_cases())
|
||||
def test_server_migration(
|
||||
accounts: List[Account], requested_url: str, expected: List[Account]
|
||||
) -> None:
|
||||
_add_accounts(accounts)
|
||||
try:
|
||||
res = get_accounts_for_server(urlparse(requested_url).netloc)
|
||||
assert res == expected
|
||||
|
||||
finally:
|
||||
_clean_accounts(accounts)
|
||||
Reference in New Issue
Block a user