diff --git a/mephisto/abstractions/providers/prolific/prolific_datastore.py b/mephisto/abstractions/providers/prolific/prolific_datastore.py index 50e8b3e5e..32cc2a8e5 100644 --- a/mephisto/abstractions/providers/prolific/prolific_datastore.py +++ b/mephisto/abstractions/providers/prolific/prolific_datastore.py @@ -13,7 +13,6 @@ from mephisto.abstractions.databases.local_database import is_unique_failure from mephisto.abstractions.providers.prolific.provider_type import PROVIDER_TYPE -from mephisto.abstractions.providers.prolific.prolific_utils import get_prolific_api_key from mephisto.utils.logger_core import get_logger CREATE_REQUESTERS_TABLE = """ @@ -64,7 +63,7 @@ class ProlificDatastore: def __init__(self, datastore_root: str): """Initialize local storage of active agents, connect to the database""" - self.session_storage: Dict[str, Any] = {} # TODO (FB-3): Implement type + self.session_storage: Dict[str, Any] = {} # TODO (#1008): Implement type self.agent_data: Dict[str, Dict[str, Any]] = {} self.table_access_condition = threading.Condition() self.conn: Dict[int, sqlite3.Connection] = {} @@ -239,14 +238,13 @@ def get_unit_expired(self, unit_id: str) -> bool: results = c.fetchall() return bool(results[0]["is_expired"]) - def get_session_for_requester(self, requester_name: str) -> Any: # TODO (FB-3): Implement type + def get_session_for_requester(self, requester_name: str) -> Any: # TODO (#1008): Implement type """ Either create a new session for the given requester or return the existing one if it has already been created """ if requester_name not in self.session_storage: - session = None # TODO (FB-3): Implement client - session.api_key = os.environ.get('PROLIFIC_API_KEY', None) or get_prolific_api_key() + session = None # TODO (#1008): Implement client self.session_storage[requester_name] = session return self.session_storage[requester_name] @@ -261,7 +259,7 @@ def get_client_for_requester(self, requester_name: str) -> Any: def get_qualification_mapping(self, qualification_name: str) -> Optional[sqlite3.Row]: """ Get the mapping between Mephisto qualifications and Prolific qualifications - (Surger Teams https://app.surgehq.ai/docs/api#surger-teams) # TODO (FB-3): Change doc + # TODO (#1008): Change doc """ with self.table_access_condition: conn = self._get_connection() diff --git a/mephisto/abstractions/providers/prolific/prolific_utils.py b/mephisto/abstractions/providers/prolific/prolific_utils.py index d0eba61f6..c624bf92e 100644 --- a/mephisto/abstractions/providers/prolific/prolific_utils.py +++ b/mephisto/abstractions/providers/prolific/prolific_utils.py @@ -16,39 +16,28 @@ from mephisto.abstractions.crowd_provider import ProviderArgs from mephisto.utils.logger_core import get_logger +from mephisto.utils.prolific_api.base_api_resource import CREDENTIALS_CONFIG_DIR +from mephisto.utils.prolific_api.base_api_resource import CREDENTIALS_CONFIG_PATH +from mephisto.utils import prolific_api PROLIFIC_BUDGET = 100000.0 -PROLIFIC_CONFIG_DIR = '~/.prolific/' -PROLIFIC_CONFIG_PATH = os.path.join(PROLIFIC_CONFIG_DIR, 'credentials') logger = get_logger(name=__name__) -def get_prolific_api_key() -> Union[str, None]: - credentials_path = os.path.expanduser(PROLIFIC_CONFIG_PATH) - if os.path.exists(credentials_path): - with open(credentials_path, 'r') as f: - api_key = f.read().strip() - return api_key - return None - - -PROLIFIC_API_KEY = os.environ.get('PROLIFIC_API_KEY', None) or get_prolific_api_key() - - def check_credentials(*args, **kwargs) -> bool: """Check whether API KEY is correct""" - # TODO (FB-3): Implement + # TODO (#1008): Implement pass def setup_credentials( profile_name: str, register_args: Optional[ProviderArgs], ) -> bool: - if not os.path.exists(os.path.expanduser(PROLIFIC_CONFIG_DIR)): - os.mkdir(os.path.expanduser(PROLIFIC_CONFIG_DIR)) + if not os.path.exists(os.path.expanduser(CREDENTIALS_CONFIG_DIR)): + os.mkdir(os.path.expanduser(CREDENTIALS_CONFIG_DIR)) - with open(os.path.expanduser(PROLIFIC_CONFIG_PATH), 'w') as f: + with open(os.path.expanduser(CREDENTIALS_CONFIG_PATH), 'w') as f: f.write(register_args.api_key) return True @@ -60,25 +49,25 @@ def check_balance(*args, **kwargs) -> Union[float, int]: requester account, returns True if the balance is greater than balance_needed """ - # TODO (FB-3): Implement + # TODO (#1008): Implement return PROLIFIC_BUDGET def delete_qualification( - client: Any, # TODO (FB-3): Implement + client: prolific_api, # TODO (#1008): Implement qualification_id: str, ) -> None: """Deletes a qualification by id""" - client.Team.delete(qualification_id) + client.ParticipantGroups.delete(qualification_id) def find_qualification( - client: Any, # TODO (FB-3): Implement + client: prolific_api, # TODO (#1008): Implement qualification_name: str, ) -> Tuple[bool, Optional[str]]: try: - qualifications = [] # TODO (FB-3): Implement - except Exception: # TODO (FB-3): Implement + qualifications = [] # TODO (#1008): Implement + except Exception: # TODO (#1008): Implement logger.exception(f'Could not receive a qualifications') raise @@ -90,7 +79,7 @@ def find_qualification( def find_or_create_qualification( - client: Any, # TODO (FB-3): Implement + client: prolific_api, # TODO (#1008): Implement qualification_name: str, description: str, ) -> Optional[str]: @@ -100,9 +89,9 @@ def find_or_create_qualification( return qualification_id try: - # TODO (FB-3): Implement + # TODO (#1008): Implement qualification = None - except Exception: # TODO (FB-3): Implement + except Exception: # TODO (#1008): Implement logger.exception( f'Could not create a qualification with name "{qualification_name}" ' f'and description "{description}"' @@ -113,7 +102,7 @@ def find_or_create_qualification( def create_study( - client: Any, # TODO (FB-3): Implement + client: prolific_api, # TODO (#1008): Implement task_args: "DictConfig", qualifications: List[Dict[str, Any]], ) -> str: @@ -122,9 +111,9 @@ def create_study( instructions = task_args.task_description try: - # TODO (FB-3): Implement + # TODO (#1008): Implement pass - except Exception: # TODO (FB-3): Implement + except Exception: # TODO (#1008): Implement logger.exception( f'Could not create a Study with name "{name}" ' f'and instructions "{instructions}"' diff --git a/mephisto/utils/prolific_api/__init__.py b/mephisto/utils/prolific_api/__init__.py index 240697e32..63bfe2bfd 100644 --- a/mephisto/utils/prolific_api/__init__.py +++ b/mephisto/utils/prolific_api/__init__.py @@ -3,3 +3,9 @@ # Copyright (c) Facebook, Inc. and its affiliates. # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. + +from .participant_groups import ParticipantGroups +from .projects import Projects +from .studies import Studies +from .users import Users +from .workspaces import Workspaces diff --git a/mephisto/utils/prolific_api/base_api_resource.py b/mephisto/utils/prolific_api/base_api_resource.py index d458fd579..87399314c 100644 --- a/mephisto/utils/prolific_api/base_api_resource.py +++ b/mephisto/utils/prolific_api/base_api_resource.py @@ -6,6 +6,7 @@ import os from typing import Optional +from typing import Union from urllib.parse import urljoin import requests @@ -17,12 +18,25 @@ from .exceptions import ProlificException from .exceptions import ProlificRequestError -API_KEY = os.environ.get('PROLIFIC_API_KEY', '') BASE_URL = os.environ.get('PROLIFIC_BASE_URL', 'https://api.prolific.co/api/v1/') +CREDENTIALS_CONFIG_DIR = '~/.surge_ai/' +CREDENTIALS_CONFIG_PATH = os.path.join(CREDENTIALS_CONFIG_DIR, 'credentials') logger = get_logger(name=__name__) +def get_surge_ai_api_key() -> Union[str, None]: + credentials_path = os.path.expanduser(CREDENTIALS_CONFIG_PATH) + if os.path.exists(credentials_path): + with open(credentials_path, 'r') as f: + api_key = f.read().strip() + return api_key + return None + + +API_KEY = os.environ.get('PROLIFIC_API_KEY', '') or get_surge_ai_api_key() + + class HTTPMethod: GET = 'get' POST = 'post' @@ -69,7 +83,9 @@ def _base_request( raise ProlificException('Invalid HTTP method.') response.raise_for_status() - return response.json() + json = response.json() + logger.debug(f'Response: {json}') + return json except requests.exceptions.HTTPError as err: logger.error(f'Request error: {str(err)}') diff --git a/mephisto/utils/prolific_api/constants.py b/mephisto/utils/prolific_api/constants.py new file mode 100644 index 000000000..0d568fd04 --- /dev/null +++ b/mephisto/utils/prolific_api/constants.py @@ -0,0 +1 @@ +EMAIL_FORMAT = '^\\S+@\\S+\\.\\S+$' # Simple email format checking diff --git a/mephisto/utils/prolific_api/data_models/__init__.py b/mephisto/utils/prolific_api/data_models/__init__.py index 5e6cb122d..924c02bbe 100644 --- a/mephisto/utils/prolific_api/data_models/__init__.py +++ b/mephisto/utils/prolific_api/data_models/__init__.py @@ -4,6 +4,9 @@ # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. +from .participant import Participant from .participant_group import ParticipantGroup +from .project import Project from .study import Study from .user import User +from .workspace import Workspace diff --git a/mephisto/utils/prolific_api/data_models/base_model.py b/mephisto/utils/prolific_api/data_models/base_model.py index eb80746df..e91119fe8 100644 --- a/mephisto/utils/prolific_api/data_models/base_model.py +++ b/mephisto/utils/prolific_api/data_models/base_model.py @@ -10,7 +10,7 @@ class BaseModel: id: str - _schema = { + schema = { 'type' : 'object', 'properties' : { 'id' : { @@ -19,9 +19,24 @@ class BaseModel: }, } + required_schema_fields = [] + id_field_name = 'id' + def __init__(self, **data): - validate(instance=data, schema=self._schema) + schema = dict(self.schema) + + # Validate on required fields if object is intended to be created + if self.id_field_name not in data: + schema['required'] = self.required_schema_fields + + validate(instance=data, schema=schema) self.__dict__ = data + def __str__(self) -> str: + return f'{self.__class__.__name__} {getattr(self, "id", "")}' + + def __repr__(self) -> str: + return f'<{self.__str__()}>' + def to_dict(self) -> dict: return self.__dict__ diff --git a/mephisto/utils/prolific_api/data_models/participant.py b/mephisto/utils/prolific_api/data_models/participant.py new file mode 100644 index 000000000..bf5577c2f --- /dev/null +++ b/mephisto/utils/prolific_api/data_models/participant.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 + +# Copyright (c) Facebook, Inc. and its affiliates. +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from .base_model import BaseModel + + +class Participant(BaseModel): + participant_id: str + datetime_created: str + + schema = { + 'type' : 'object', + 'properties' : { + 'participant_id' : { + 'type' : 'string', + }, + 'datetime_created' : { + 'type' : 'string', + }, + }, + } + + id_field_name = 'participant_id' + + def __str__(self) -> str: + return f'{self.__class__.__name__} {self.participant_id}' diff --git a/mephisto/utils/prolific_api/data_models/participant_group.py b/mephisto/utils/prolific_api/data_models/participant_group.py index b0dbc4cbc..a3c1a75c4 100644 --- a/mephisto/utils/prolific_api/data_models/participant_group.py +++ b/mephisto/utils/prolific_api/data_models/participant_group.py @@ -4,17 +4,64 @@ # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. +from typing import Dict +from typing import List + from .base_model import BaseModel class ParticipantGroup(BaseModel): id: str + name: str + project_id: str + participant_count: int + feeder_studies: List[Dict] - _schema = { + schema = { 'type' : 'object', 'properties' : { 'id' : { 'type' : 'string', }, + 'project_id' : { + 'type' : 'string', + }, + 'name' : { + 'type' : 'string', + }, + 'participant_count' : { + 'type' : 'number', + }, + 'feeder_studies' : { + 'type' : 'array', + 'items': { + 'type': 'object', + 'properties': { + 'id': { + 'type': 'string', + }, + 'name': { + 'type': 'string', + }, + 'internal_name': { + 'type': 'string', + }, + 'status': { + 'type': 'string', + }, + 'completion_codes' : { + 'type' : 'array', + }, + }, + }, + }, }, } + + required_schema_fields = [ + 'project_id', + 'name', + ] + + def __str__(self) -> str: + return f'{self.__class__.__name__} {self.id} {self.name}' diff --git a/mephisto/utils/prolific_api/data_models/project.py b/mephisto/utils/prolific_api/data_models/project.py new file mode 100644 index 000000000..0b71bfa2d --- /dev/null +++ b/mephisto/utils/prolific_api/data_models/project.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 + +# Copyright (c) Facebook, Inc. and its affiliates. +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from decimal import Decimal +from typing import Dict +from typing import List +from typing import Optional +from typing import Union + +from .base_model import BaseModel +from .user import User + + +class Project(BaseModel): + id: str + title: str + description: str + owner: str + users: List[Dict] + studies: List[Dict] + workspace: str + naivety_distribution_rate: Optional[Union[Decimal, float]] + + schema = { + 'type': 'object', + 'properties': { + 'id': { + 'type': 'string', + }, + 'title': { + 'type': 'string', + }, + 'description': { + 'type': 'string', + }, + 'owner': { + 'type': 'string', + }, + 'users' : { + 'type' : 'array', + 'items': User.relation_user_schema, + }, + 'studies' : { + 'type' : 'array', + 'items': { + 'type': 'object', + 'properties': { + 'id': { + 'type': 'string', + }, + 'name': { + 'type': 'string', + }, + }, + } + }, + 'workspace': { + 'type': 'string', + }, + 'naivety_distribution_rate' : { + 'type' : ['number', 'null'], + }, + }, + } + + required_schema_fields = [ + 'id', + 'title', + ] + + def __str__(self) -> str: + return f'{self.__class__.__name__} {self.id} {self.title}' diff --git a/mephisto/utils/prolific_api/data_models/study.py b/mephisto/utils/prolific_api/data_models/study.py index 5f748c8ca..fffbdeff2 100644 --- a/mephisto/utils/prolific_api/data_models/study.py +++ b/mephisto/utils/prolific_api/data_models/study.py @@ -5,6 +5,7 @@ # LICENSE file in the root directory of this source tree. from typing import List +from typing import Optional from .base_model import BaseModel @@ -15,16 +16,19 @@ class Study(BaseModel): internal_name: str description: str external_study_url: str - prolific_id_option: str - completion_codes: List[dict] + prolific_id_option: Optional[str] + completion_codes: Optional[List[dict]] total_available_places: int estimated_completion_time: int reward: int device_compatibility: List[str] peripheral_requirements: List eligibility_requirements: List + project: str + submissions_config: dict + metadata: Optional[str] - _schema = { + schema = { 'type' : 'object', 'properties' : { 'id' : { @@ -43,13 +47,13 @@ class Study(BaseModel): 'type' : 'string', }, 'prolific_id_option' : { - 'type' : 'string', + 'type' : ['string', 'null'], 'items': { 'enum': ['question', 'url_parameters', 'not_required'], }, }, 'completion_option' : { - 'type' : 'string', + 'type' : ['string', 'null'], 'items': { 'enum': ['url', 'code'], }, @@ -72,8 +76,8 @@ class Study(BaseModel): 'required': [ 'code', 'code_type', - ] - } + ], + }, }, 'total_available_places' : { 'type' : 'number', @@ -96,17 +100,30 @@ class Study(BaseModel): 'eligibility_requirements' : { 'type' : 'array', }, + 'project' : { + 'type' : 'string', + }, + 'submissions_config': { + 'type': 'object', + }, + 'metadata' : { + 'type' : ['string', 'null'], + }, }, - 'required': [ - 'name', - 'description', - 'external_study_url', - 'prolific_id_option', - 'completion_option', - 'completion_codes', - 'total_available_places', - 'estimated_completion_time', - 'reward', - 'eligibility_requirements', - ] } + + required_schema_fields = [ + 'name', + 'description', + 'external_study_url', + 'prolific_id_option', + 'completion_option', + 'completion_codes', + 'total_available_places', + 'estimated_completion_time', + 'reward', + 'eligibility_requirements', + ] + + def __str__(self) -> str: + return f'{self.__class__.__name__} {self.id} {self.name}' diff --git a/mephisto/utils/prolific_api/data_models/user.py b/mephisto/utils/prolific_api/data_models/user.py index 95a893568..6516ff6a5 100644 --- a/mephisto/utils/prolific_api/data_models/user.py +++ b/mephisto/utils/prolific_api/data_models/user.py @@ -5,13 +5,14 @@ # LICENSE file in the root directory of this source tree. from .base_model import BaseModel +from ..constants import EMAIL_FORMAT class User(BaseModel): id: str email: str - _schema = { + schema = { 'type' : 'object', 'properties' : { 'id' : { @@ -19,7 +20,32 @@ class User(BaseModel): }, 'email' : { 'type' : 'string', - 'pattern': '^\\S+@\\S+\\.\\S+$', # Simple checking + 'pattern': EMAIL_FORMAT, }, }, } + + relation_user_schema = { + 'type': 'object', + 'properties': { + 'id': { + 'type': 'string', + }, + 'name': { + 'type': 'string', + }, + 'email': { + 'type': 'string', + 'pattern': EMAIL_FORMAT, + }, + 'roles': { + 'type': 'array', + }, + }, + 'required': [ + 'id', + ] + } + + def __str__(self) -> str: + return f'{self.__class__.__name__} {self.id} {self.email}' diff --git a/mephisto/utils/prolific_api/data_models/workspace.py b/mephisto/utils/prolific_api/data_models/workspace.py new file mode 100644 index 000000000..11ee73462 --- /dev/null +++ b/mephisto/utils/prolific_api/data_models/workspace.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 + +# Copyright (c) Facebook, Inc. and its affiliates. +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from decimal import Decimal +from typing import Dict +from typing import List +from typing import Optional +from typing import Union + +from .base_model import BaseModel +from .user import User + + +class Workspace(BaseModel): + id: str + title: str + description: str + owner: str + users: List[Dict] + projects: List[Dict] + wallet: str + naivety_distribution_rate: Optional[Union[Decimal, float]] + + schema = { + 'type' : 'object', + 'properties' : { + 'id' : { + 'type' : 'string', + }, + 'title' : { + 'type' : 'string', + }, + 'description' : { + 'type' : 'string', + }, + 'owner' : { + 'type' : 'string', + }, + 'users' : { + 'type' : 'array', + 'items': User.relation_user_schema, + }, + 'projects' : { + 'type' : 'array', + 'items': { + 'type': 'object', + 'properties': { + 'id': { + 'type': 'string', + }, + 'title': { + 'type': 'string', + }, + 'description': { + 'type': 'string', + }, + 'owner': { + 'type': 'string', + }, + 'users' : { + 'type' : 'array', + 'items': User.relation_user_schema, + }, + 'naivety_distribution_rate' : { + 'type' : ['number', 'null'], + }, + }, + }, + }, + 'wallet' : { + 'type' : 'string', + }, + 'naivety_distribution_rate' : { + 'type' : ['number', 'null'], + }, + }, + } + + def __str__(self) -> str: + return f'{self.__class__.__name__} {self.id} {self.title}' diff --git a/mephisto/utils/prolific_api/participant_groups.py b/mephisto/utils/prolific_api/participant_groups.py new file mode 100644 index 000000000..733ea3f1a --- /dev/null +++ b/mephisto/utils/prolific_api/participant_groups.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 + +# Copyright (c) Facebook, Inc. and its affiliates. +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from typing import List +from typing import Optional + +from .base_api_resource import BaseAPIResource +from .data_models import Participant +from .data_models import ParticipantGroup + + +class ParticipantGroups(BaseAPIResource): + list_api_endpoint = 'participant-groups/' + list_perticipants_for_group_api_endpoint = 'participant-groups/{id}/participants/' + + @classmethod + def list(cls, project_id: Optional[str] = None) -> List[ParticipantGroup]: + endpoint = cls.list_api_endpoint + if project_id: + endpoint = f'{endpoint}?project_id={project_id}' + + response_json = cls.get(endpoint) + participant_groups = [ParticipantGroup(**s) for s in response_json['results']] + return participant_groups + + @classmethod + def create(cls, **data) -> ParticipantGroup: + participant_group = ParticipantGroup(**data) + response_json = cls.post(cls.list_api_endpoint, params=participant_group.to_dict()) + return ParticipantGroup(**response_json) + + @classmethod + def list_perticipants_for_group(cls, id: str) -> List[Participant]: + response_json = cls.get(cls.list_perticipants_for_group_api_endpoint.format(id=id)) + participants = [Participant(**s) for s in response_json['results']] + return participants diff --git a/mephisto/utils/prolific_api/projects.py b/mephisto/utils/prolific_api/projects.py new file mode 100644 index 000000000..f22e7e674 --- /dev/null +++ b/mephisto/utils/prolific_api/projects.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 + +# Copyright (c) Facebook, Inc. and its affiliates. +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from typing import List + +from .base_api_resource import BaseAPIResource +from .data_models import Project + + +class Projects(BaseAPIResource): + list_for_workspace_api_endpoint = 'workspaces/{workspace_id}/projects/' + retrieve_for_workspace_api_endpoint = 'workspaces/{workspace_id}/projects/{project_id}/' + + @classmethod + def list_for_workspace(cls, workspace_id: str) -> List[Project]: + endpoint = cls.list_for_workspace_api_endpoint.format(workspace_id=workspace_id) + response_json = cls.get(endpoint) + projects = [Project(**s) for s in response_json['results']] + return projects + + @classmethod + def retrieve_for_workspace(cls, workspace_id: str, project_id: str) -> Project: + endpoint = cls.retrieve_for_workspace_api_endpoint.format( + workspace_id=workspace_id, project_id=project_id, + ) + response_json = cls.get(endpoint) + return Project(**response_json) + + @classmethod + def create(cls, workspace_id: str, **data) -> Project: + project = Project(**data) + endpoint = cls.list_for_workspace_api_endpoint.format(workspace_id=workspace_id) + response_json = cls.post(endpoint, params=project.to_dict()) + return Project(**response_json) diff --git a/mephisto/utils/prolific_api/studies.py b/mephisto/utils/prolific_api/studies.py index ca5158466..2e198c604 100644 --- a/mephisto/utils/prolific_api/studies.py +++ b/mephisto/utils/prolific_api/studies.py @@ -4,29 +4,39 @@ # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. +from typing import List + from .base_api_resource import BaseAPIResource from .data_models import Study class Studies(BaseAPIResource): - list_api_endpoint = 'studies' - retrieve_api_endpoint = 'studies/{id}' - delete_api_endpoint = 'studies/{id}' + list_api_endpoint = 'studies/' + list_for_project_api_endpoint = 'projects/{project_id}/studies/' + retrieve_api_endpoint = 'studies/{id}/' + delete_api_endpoint = 'studies/{id}/' @classmethod - def list(cls): + def list(cls) -> List[Study]: response_json = cls.get(cls.list_api_endpoint) - projects = [Study(**s) for s in response_json] - return projects + studies = [Study(**s) for s in response_json['results']] + return studies + + @classmethod + def list_for_project(cls, project_id: str) -> List[Study]: + endpoint = cls.list_for_project_api_endpoint.format(project_id=project_id) + response_json = cls.get(endpoint) + studies = [Study(**s) for s in response_json['results']] + return studies @classmethod - def retrieve(cls, id: str): + def retrieve(cls, id: str) -> Study: endpoint = cls.retrieve_api_endpoint.format(id=id) response_json = cls.get(endpoint) return Study(**response_json) @classmethod - def create(cls, **data): + def create(cls, **data) -> Study: study = Study(**data) response_json = cls.post(cls.list_api_endpoint, params=study.to_dict()) return Study(**response_json) @@ -34,4 +44,4 @@ def create(cls, **data): @classmethod def remove(cls, id: str): endpoint = cls.retrieve_api_endpoint.format(id=id) - return cls.delete(endpoint) + cls.delete(endpoint) diff --git a/mephisto/utils/prolific_api/users.py b/mephisto/utils/prolific_api/users.py index 2f137100f..594f54507 100644 --- a/mephisto/utils/prolific_api/users.py +++ b/mephisto/utils/prolific_api/users.py @@ -5,7 +5,14 @@ # LICENSE file in the root directory of this source tree. from .base_api_resource import BaseAPIResource +from .data_models import User class Users(BaseAPIResource): - pass + retrieve_api_endpoint = 'users/me/' + + @classmethod + def me(cls) -> User: + endpoint = cls.retrieve_api_endpoint + response_json = cls.get(endpoint) + return User(**response_json) diff --git a/mephisto/utils/prolific_api/workspaces.py b/mephisto/utils/prolific_api/workspaces.py new file mode 100644 index 000000000..0c0f3ecea --- /dev/null +++ b/mephisto/utils/prolific_api/workspaces.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 + +# Copyright (c) Facebook, Inc. and its affiliates. +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from typing import List + +from .base_api_resource import BaseAPIResource +from .data_models import Workspace + + +class Workspaces(BaseAPIResource): + list_api_endpoint = 'workspaces/' + retrieve_api_endpoint = 'workspaces/{id}/' + + @classmethod + def list(cls) -> List[Workspace]: + response_json = cls.get(cls.list_api_endpoint) + workspaces = [Workspace(**s) for s in response_json['results']] + return workspaces + + @classmethod + def retrieve(cls, id: str) -> Workspace: + endpoint = cls.retrieve_api_endpoint.format(id=id) + response_json = cls.get(endpoint) + return Workspace(**response_json) + + @classmethod + def create(cls, **data) -> Workspace: + workspace = Workspace(**data) + response_json = cls.post(cls.list_api_endpoint, params=workspace.to_dict()) + return Workspace(**response_json) diff --git a/pytest.ini b/pytest.ini index fdb534aaa..a6f73afec 100644 --- a/pytest.ini +++ b/pytest.ini @@ -3,5 +3,6 @@ addopts = -ra -q markers = req_creds: test which requires credentials + prolific testpaths = test diff --git a/test/utils/prolific_api/test_data_models.py b/test/utils/prolific_api/test_data_models.py index dc2e388bc..0d57140d2 100644 --- a/test/utils/prolific_api/test_data_models.py +++ b/test/utils/prolific_api/test_data_models.py @@ -12,7 +12,7 @@ from mephisto.utils.prolific_api.data_models import Study -@pytest.mark.surge_ai +@pytest.mark.prolific class TestDataModelsUtils(unittest.TestCase): def test_study_validation_passed(self, *args): data = {