Shortcuts

Source code for geodesic.account.projects

import re
import warnings

import tenacity
import requests
from geodesic.bases import _APIObject
from geodesic.account import User
from geodesic import raise_on_error
from geodesic.descriptors import _BaseDescr, _RegexDescr, _StringDescr
from geodesic.service import ServiceClient
from geodesic.utils import deprecated

from typing import Union
from functools import lru_cache

# Projects client
projects_client = ServiceClient("krampus", 1, "projects")
project_name_re = re.compile(r"^(\w+[\w\-\_]*|\*)$")


@lru_cache(maxsize=None)
def _get_project(name_or_uid: str = None):
    """
    Gets a project by name or uid. It's always
    better to specify the uid to avoid ambiguity.
    The name will be used to find a project that
    the user owns, but won't check other projects
    that the name might match

    Args:
        name_or_uid: the name or uid of the project.
    """
    res = raise_on_error(projects_client.get(name_or_uid))
    p = res.json()["project"]
    if p is None:
        return None
    return Project(**p)


[docs]def get_project(name_or_uid: str = None, refresh: bool = False): """ Gets a project by name or uid. It's always better to specify the uid to avoid ambiguity. The name will be used to find a project that the user owns, but won't check other projects that the name might match Args: name_or_uid: the name or uid of the project. refresh: projects are cached by default. If you want the latest list from the server, set refresh to True """ p = _get_project(name_or_uid) if p is not None: return p if refresh: _get_project.cache_clear() p = _get_project(name_or_uid) if p is None: raise ValueError(f"no such project {name_or_uid}") return p
[docs]def get_projects(): res = raise_on_error(projects_client.get("")) return [Project(**p) for p in res.json()["projects"]]
[docs]def create_project(name: str, alias: str, description: str, keywords: list = []) -> "Project": """Creates a new project. Helpful instead of creating a Project instance directly. Args: name: name of the project. Used in most cases to look up a project alias: a human readable name for the project description: a text description of this project keywords: a list of keywords to describe this project. """ project = Project(name=name, alias=alias, description=description, keywords=keywords) project.create(ignore_if_exists=True) return project
[docs]class Project(_APIObject): """The Project class to manage groups of nodes in a subgraph in entanglement Args: **project: metadata about a particular project """ uid = _StringDescr(doc="unique ID set by the system") name = _RegexDescr(regex=project_name_re, doc="the name of this project, unique to the user") alias = _StringDescr(doc="a human readable name for this project/subgraph") description = _StringDescr(doc="a description of this project/subgraph") owner = _StringDescr(doc="the subject (user id) of this owner of this project") def __init__(self, **project): self._client = projects_client super().__init__(self, **project)
[docs] def create(self, ignore_if_exists=True) -> None: """ Creates new project for this object """ try: raise_on_error(self._client.post("", project=self)) except requests.HTTPError as e: if ignore_if_exists: try: project = get_project(self.name) self._set_item("uid", project.uid) self._set_item("owner", project.owner) return except requests.HTTPError: raise e raise e @tenacity.retry(wait=tenacity.wait_fixed(2), stop=tenacity.stop_after_attempt(3)) def _get_project(name: str): project = get_project(name) return project project = _get_project(self.name) self._set_item("uid", project.uid) self._set_item("owner", project.owner)
[docs] def delete(self) -> None: """ Deletes this project """ raise_on_error(self._client.delete(self.name))
[docs] def update_permissions(self, user: Union[User, str], permissions: dict): """ Updates the read/write access for a user on this project. Arguments: user: The User (or subject) to update permissions for permissions: a dictionary of the read/write for the user Example: >>> p.update_permissions(user, {'read': True, 'write': False}) """ sub = None if isinstance(user, User): sub = user.subject elif isinstance(user, str): sub = user if sub is None: raise ValueError("must specify a user as a User or subject (str)") for k, v in permissions.items(): if k not in ["read", "write"]: raise ValueError("can only set read or write as permissions") if not isinstance(v, bool): raise ValueError("permissions must be boolean values for read/write") raise_on_error(self._client.put(f"{self.uid}/permission/{sub}", **permissions))
update_permission = deprecated("v1.0.0", "Project.update_permission")(update_permissions)
[docs] def get_permissions(self, user: User): """ Gets the read/write permissions of a user on this project Arguments: user: The user to check permissions for. """ res = raise_on_error(self._client.get(f"{self.uid}/permission/{user.subject}")) return res.json()
permission = deprecated("v1.0.0", "Project.permission")(get_permissions) @property def keywords(self): """ Keywords related to this project """ return list(map(str.strip, self["keywords"].split(","))) @keywords.setter def keywords(self, v: Union[list, str]): if isinstance(v, str): self._set_item("keywords", v) return elif not isinstance(v, (list, tuple)): raise ValueError("keywords must be a list of strings") self._set_item("keywords", ", ".join(v))
# Only one project can be active at one time. Certain functions (e.g. in Entanglement) # will reference this project. This is the global project by default. active_project = None
[docs]def set_active_project(p: Union[Project, str]) -> Project: """ Sets the active project. Can either be a project name/uid or a Project """ global active_project if isinstance(p, (Project, dict)): active_project = Project(**p) else: active_project = get_project(p) if active_project is None: raise ValueError(f"unknown project {p}") return active_project
[docs]def get_active_project() -> Project: """ Gets the active project. If none exists, returns a handle to the 'global' project. """ global active_project if active_project is None: return set_active_project("global") return active_project
class _ProjectDescr(_BaseDescr): """:class:`_ProjectDescr<geodesic.account.projects._ProjectDescr>` A geodesic Project/Entanglement Subgraph Returns: a Project object, sets the project name on the base object """ def _get(self, obj: object, objtype=None) -> dict: # Try to get the private attribute by name (e.g. '_project') project = getattr(obj, self.private_name, None) if project is not None: # Return it if it exists return project try: project_uid = self._get_object(obj) project = get_project(project_uid) setattr(obj, self.private_name, project) except KeyError: self._attribute_error(objtype) return project def _set(self, obj: object, value: object) -> None: # Reset the private attribute (e.g. "_project") to None setattr(obj, self.private_name, None) if isinstance(value, (Project, dict)): self._set_object(obj, Project(**value).uid) elif isinstance(value, str): p = get_project(value) self._set_object(obj, p.uid) else: raise ValueError(f"invalid value type {type(value)}") def _validate(self, obj: object, value: object) -> None: if not isinstance(value, (Project, str, dict)): raise ValueError(f"'{self.public_name}' must be a Project or a string") # If the project was set, we need to validate that it exists and the user has access project_name = None if isinstance(value, str): project_name = value else: project_name = Project(**value).uid try: get_project(project_name, refresh=True) except Exception as e: raise ValueError( f"project '{project_name}' does not exist or user doesn't have access" ) from e

Docs

Developer documentation for Seer AI APIs

View Docs

Tutorials

Get in-depth tutorials for beginners and advanced developers

View Tutorials

Resources

Find development resources and get your questions answered

View Resources