Skip to content
Open
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
16 changes: 11 additions & 5 deletions surge/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,18 @@ class Project(APIResource):

def __init__(self, **kwargs):
super().__init__()
self.__dict__.update(kwargs)
self._update(**kwargs)

if self.id is None:
raise SurgeMissingIDError

if not (hasattr(self, "name") and self.name):
raise SurgeMissingAttributeError


def _update(self, **kwargs):
self.__dict__.update(kwargs)

if hasattr(self, "created_at") and self.created_at:
# Convert timestamp str into datetime
self.created_at = dateutil.parser.parse(self.created_at)
Expand All @@ -39,6 +43,7 @@ def __init__(self, **kwargs):
if hasattr(self, "questions"):
self.questions = self._convert_questions_to_objects(self.questions)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The project is mutated in-place but the questions are re-instantiated. Is that desirable?
It seems inconsistent.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely not desirable, but it seems relatively difficult to update questions in-place so I'd like to punt on that - see https://github.com/surge-ai/surge-python/blob/main/surge/questions.py#L41



def __str__(self):
return f'<surge.Project#{self.id} name="{self.name}">'

Expand Down Expand Up @@ -360,9 +365,9 @@ def update(
description: str = None,
params: dict = {},
api_key: str = None,
):
) -> 'Project':
"""
Update an existing project
Update an existing project, and updates the project object in-place

Arguments:
name (str): Name of the project.
Expand All @@ -375,7 +380,7 @@ def update(
num_workers_per_task (int, optional): How many workers work on each task (i.e., how many responses per task).

Returns:
project: new Project object
project: updated Project object
"""

params = {**params}
Expand All @@ -397,7 +402,8 @@ def update(

endpoint = f"{PROJECTS_ENDPOINT}/{self.id}"
response_json = self.put(endpoint, params, api_key=api_key)
return Project(**response_json)
self._update(**response_json)
return self

def workable_by_surger(self, surger_id, api_key: str = None):
"""
Expand Down
54 changes: 54 additions & 0 deletions tests/test_projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -459,3 +459,57 @@ def test_update_with_fields_template():
{"fields_text": "ABC"},
api_key=None,
)


def test_update_modifies_project_in_place():
"""Test that update() modifies the project object in-place based on PUT response."""
project = Project(
id="UPDATE_IN_PLACE",
name="Original Name",
status="draft",
payment_per_response=0.10
)

# Store original object id to verify same instance is returned
original_object_id = id(project)

# Mock the PUT response with updated attributes
put_response = {
"id": "UPDATE_IN_PLACE",
"name": "Updated Name",
"status": "launched",
"payment_per_response": 0.25,
"description": "New description added",
"instructions": "Updated instructions"
}

with patch.object(Project, "put") as mock_put:
mock_put.return_value = put_response

# Verify initial state
assert project.name == "Original Name"
assert project.status == "draft"
assert project.payment_per_response == 0.10
assert not hasattr(project, "description")
assert not hasattr(project, "instructions")

# Call update
updated_project = project.update(name="Updated Name")

# Verify the same object instance is returned
assert updated_project is project
assert id(updated_project) == original_object_id

# Verify project attributes were updated in-place from PUT response
assert project.name == "Updated Name"
assert project.status == "launched"
assert project.payment_per_response == 0.25
assert project.description == "New description added"
assert project.instructions == "Updated instructions"

# Verify PUT was called correctly
mock_put.assert_called_once_with(
f"{PROJECTS_ENDPOINT}/{project.id}",
{"name": "Updated Name"},
api_key=None,
)