Skip to content

Commit 68c7bcf

Browse files
committed
minor improvements to typing and ability to set mlflow project with a Project object
1 parent e6722e1 commit 68c7bcf

3 files changed

Lines changed: 88 additions & 25 deletions

File tree

datamint/api/endpoints/projects_api.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Sequence, Literal, TYPE_CHECKING
1+
from typing import Sequence, Literal, TYPE_CHECKING, overload
22
from ..entity_base_api import ApiConfig, CRUDEntityApi
33
from datamint.entities.project import Project
44
import httpx
@@ -38,13 +38,38 @@ def get_project_resources(self, project: Project | str) -> list[Resource]:
3838
resources = [self.resources_api._init_entity_obj(**item) for item in resources_data]
3939
return resources
4040

41+
42+
@overload
43+
def create(self,
44+
name: str,
45+
description: str,
46+
resources_ids: list[str] | None = None,
47+
is_active_learning: bool = False,
48+
two_up_display: bool = False,
49+
*,
50+
return_entity: Literal[True] = True
51+
) -> Project: ...
52+
53+
@overload
54+
def create(self,
55+
name: str,
56+
description: str,
57+
resources_ids: list[str] | None = None,
58+
is_active_learning: bool = False,
59+
two_up_display: bool = False,
60+
*,
61+
return_entity: Literal[False]
62+
) -> str: ...
63+
4164
def create(self,
4265
name: str,
4366
description: str,
4467
resources_ids: list[str] | None = None,
4568
is_active_learning: bool = False,
46-
two_up_display: bool = False
47-
) -> str:
69+
two_up_display: bool = False,
70+
*,
71+
return_entity: bool = True
72+
) -> str | Project:
4873
"""Create a new project.
4974
5075
Args:
@@ -53,6 +78,7 @@ def create(self,
5378
resources_ids: The list of resource ids to be included in the project.
5479
is_active_learning: Whether the project is an active learning project or not.
5580
two_up_display: Allow annotators to display multiple resources for annotation.
81+
return_entity: Whether to return the created Project instance or just its ID.
5682
5783
Returns:
5884
The id of the created project.
@@ -72,7 +98,7 @@ def create(self,
7298
"require_review": False,
7399
'description': description}
74100

75-
return self._create(project_data)
101+
return self._create(project_data, return_entity=return_entity)
76102

77103
def get_all(self, limit: int | None = None) -> Sequence[Project]:
78104
"""Get all projects.

datamint/api/entity_base_api.py

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Any, TypeVar, Generic, Type, Sequence
1+
from typing import Any, Literal, TypeVar, Generic, Type, Sequence, AsyncGenerator, overload
22
import logging
33
import httpx
44
from datamint.entities.base_entity import BaseEntity
@@ -7,7 +7,6 @@
77
import asyncio
88
from .base_api import ApiConfig, BaseApi
99
import contextlib
10-
from typing import AsyncGenerator
1110

1211
logger = logging.getLogger(__name__)
1312
T = TypeVar('T', bound=BaseEntity)
@@ -248,7 +247,16 @@ class CreatableEntityApi(EntityBaseApi[T]):
248247
This class adds methods to handle creation of new entities.
249248
"""
250249

251-
def _create(self, entity_data: dict[str, Any]) -> str | list[str | dict]:
250+
@overload
251+
def _create(self, entity_data: dict[str, Any],
252+
return_entity: Literal[True] = True) -> T | list[T]: ...
253+
254+
@overload
255+
def _create(self, entity_data: dict[str, Any],
256+
return_entity: Literal[False]) -> str | list: ...
257+
258+
def _create(self, entity_data: dict[str, Any],
259+
return_entity: bool = False) -> str | T | list:
252260
"""Create a new entity.
253261
254262
Args:
@@ -263,14 +271,35 @@ def _create(self, entity_data: dict[str, Any]) -> str | list[str | dict]:
263271
response = self._make_request('POST', f'/{self.endpoint_base}', json=entity_data)
264272
respdata = response.json()
265273
if isinstance(respdata, str):
274+
if return_entity:
275+
return self.get_by_id(respdata)
266276
return respdata
267277
if isinstance(respdata, list):
278+
if return_entity:
279+
logger.warning("Current implementation is slow when returning entities on bulk create."
280+
" Try ``return_entity=False`` for better performance.")
281+
return [self.get_by_id(item['id']) if isinstance(item, dict) and 'id' in item else self.get_by_id(item)
282+
for item in respdata]
268283
return respdata
269284
if isinstance(respdata, dict):
285+
if return_entity:
286+
try:
287+
return self._init_entity_obj(**respdata)
288+
except:
289+
logger.debug("Failed to init entity obj on create response. Falling back to get_by_id.")
290+
return self.get_by_id(respdata.get('id'))
270291
return respdata.get('id')
271292
return respdata
272293

273-
def create(self, *args, **kwargs) -> str | T:
294+
@overload
295+
def create(self, *args, return_entity: Literal[True] = True, **kwargs) -> T: ...
296+
297+
@overload
298+
def create(self, *args, return_entity: Literal[False], **kwargs) -> str: ...
299+
300+
def create(self, *args,
301+
return_entity: bool = True,
302+
**kwargs) -> str | T:
274303
raise NotImplementedError("Subclasses must implement the create method with their own custom parameters")
275304

276305

datamint/mlflow/tracking/fluent.py

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Optional
1+
from typing import Optional, TYPE_CHECKING
22
import threading
33
import logging
44
from datamint import Api
@@ -7,6 +7,9 @@
77
from datamint.mlflow.env_vars import EnvVars
88
from datamint.mlflow.env_utils import ensure_mlflow_configured
99

10+
if TYPE_CHECKING:
11+
from datamint.entities.project import Project
12+
1013
_PROJECT_LOCK = threading.Lock()
1114
_LOGGER = logging.getLogger(__name__)
1215

@@ -44,30 +47,35 @@ def _find_project_by_name(project_name: str):
4447
return project
4548

4649

47-
def set_project(project_name: Optional[str] = None, project_id: Optional[str] = None):
48-
from mlflow.exceptions import MlflowException
50+
def _get_project_by_name_or_id(project_name_or_id: str) -> 'Project':
51+
dt_client = Api(check_connection=False)
52+
# If length >= 32, likely an ID
53+
if len(project_name_or_id) >= 32 and ' ' not in project_name_or_id:
54+
# Try to get by ID first
55+
project = dt_client.projects.get_by_id(project_name_or_id)
56+
if project is not None:
57+
return project
58+
project = dt_client.projects.get_by_name(project_name_or_id)
59+
if project is None:
60+
raise DatamintException(f"Project '{project_name_or_id}' does not exist.")
61+
return project
62+
63+
64+
def set_project(project: 'Project | str'):
65+
from datamint.entities.project import Project as ProjectEntity
4966
global _ACTIVE_PROJECT_ID
5067

5168
# Ensure MLflow is properly configured before proceeding
5269
ensure_mlflow_configured()
5370

54-
if project_name is None and project_id is None:
55-
raise MlflowException("You must specify either a project name or a project id")
56-
57-
if project_name is not None and project_id is not None:
58-
raise MlflowException("You cannot specify both a project name and a project id")
59-
6071
with _PROJECT_LOCK:
61-
dt_client = Api(check_connection=False)
62-
if project_id is None:
63-
project = dt_client.projects.get_by_name(project_name)
64-
if project is None:
65-
raise DatamintException(f"Project with name '{project_name}' does not exist.")
72+
if isinstance(project, str):
73+
project_id = None
74+
project = _get_project_by_name_or_id(project)
6675
project_id = project.id
6776
else:
68-
project = dt_client.projects.get_by_id(project_id)
69-
if project is None:
70-
raise DatamintException(f"Project with id '{project_id}' does not exist.")
77+
# It's a Project entity
78+
project_id = project.id
7179

7280
_ACTIVE_PROJECT_ID = project_id
7381

0 commit comments

Comments
 (0)