diff --git a/kindwise/core.py b/kindwise/core.py index 0546222..e0db0c9 100644 --- a/kindwise/core.py +++ b/kindwise/core.py @@ -47,12 +47,12 @@ def conversation_url(self, token: str): def conversation_feedback_url(self, token: str): return f'{self.identification_url}/{token}/conversation/feedback' - def _make_api_call(self, url, method: str, data: dict | None = None): + def _make_api_call(self, url, method: str, data: dict | None = None, timeout: float = 60.0): headers = { 'Content-Type': 'application/json', 'Api-Key': self.api_key, } - response = requests.request(method, url, json=data, headers=headers) + response = requests.request(method, url, json=data, headers=headers, timeout=timeout) if not response.ok: raise ValueError(f'Error while making an API call: {response.status_code=} {response.text=}') return response @@ -174,6 +174,7 @@ def identify( max_image_size: int | None = 1500, extra_get_params: str | dict[str | Any] = None, extra_post_params: dict[str, Any] = None, + timeout: float = 60.0, **kwargs, ) -> IdentificationType | dict: payload = self._build_payload( @@ -190,7 +191,7 @@ def identify( details=details, language=language, asynchronous=asynchronous, extra_get_params=extra_get_params, **kwargs ) url = f'{self.identification_url}{query}' - response = self._make_api_call(url, 'POST', payload) + response = self._make_api_call(url, 'POST', payload, timeout=timeout) data = response.json() return data if as_dict else self.identification_class.from_dict(data) @@ -232,26 +233,35 @@ def get_identification( language: str | list[str] = None, extra_get_params: str | dict[str, str] = None, as_dict: bool = False, + timeout: float = 60.0, ) -> IdentificationType | dict: query = self._build_query(details=details, language=language, extra_get_params=extra_get_params) url = f'{self.identification_url}/{token}{query}' - response = self._make_api_call(url, 'GET') + response = self._make_api_call(url, 'GET', timeout=timeout) data = response.json() return data if as_dict else self.identification_class.from_dict(data) - def delete_identification(self, identification: IdentificationType | str | int) -> bool: + def delete_identification( + self, + identification: IdentificationType | str | int, + timeout: float = 60.0, + ) -> bool: token = identification.access_token if isinstance(identification, Identification) else identification url = f'{self.identification_url}/{token}' - self._make_api_call(url, 'DELETE') + self._make_api_call(url, 'DELETE', timeout=timeout) return True - def usage_info(self, as_dict: bool = False) -> UsageInfo | dict: - response = self._make_api_call(self.usage_info_url, 'GET') + def usage_info(self, as_dict: bool = False, timeout: float = 60.0) -> UsageInfo | dict: + response = self._make_api_call(self.usage_info_url, 'GET', timeout=timeout) data = response.json() return data if as_dict else UsageInfo.from_dict(data) def feedback( - self, identification: IdentificationType | str | int, comment: str | None = None, rating: int | None = None + self, + identification: IdentificationType | str | int, + comment: str | None = None, + rating: int | None = None, + timeout: float = 60.0, ) -> bool: token = identification.access_token if isinstance(identification, Identification) else identification if comment is None and rating is None: @@ -261,7 +271,7 @@ def feedback( data['comment'] = comment if rating is not None: data['rating'] = rating - self._make_api_call(self.feedback_url(token), 'POST', data) + self._make_api_call(self.feedback_url(token), 'POST', data, timeout=timeout) return True @property @@ -280,6 +290,7 @@ def search( language: str = None, kb_type: KBType | str = None, as_dict=False, + timeout: float = 60.0, ) -> SearchResult | dict: if not query: raise ValueError('Query parameter q must be provided') @@ -290,20 +301,25 @@ def search( if isinstance(kb_type, enum.Enum): kb_type = kb_type.value url = f'{self.kb_api_url}/{kb_type}/name_search{self._build_query(query=query, limit=limit, language=language)}' - response = self._make_api_call(url, 'GET') + response = self._make_api_call(url, 'GET', timeout=timeout) if not response.ok: raise ValueError(f'Error while searching knowledge base: {response.status_code=} {response.text=}') return response.json() if as_dict else SearchResult.from_dict(response.json()) def get_kb_detail( - self, access_token: str, details: str | list[str], language: str = None, kb_type: KBType | str = None + self, + access_token: str, + details: str | list[str], + language: str = None, + kb_type: KBType | str = None, + timeout: float = 60.0, ) -> dict: if kb_type is None: kb_type = self.default_kb_type if isinstance(kb_type, enum.Enum): kb_type = kb_type.value url = f'{self.kb_api_url}/{kb_type}/{access_token}{self._build_query(language=language, details=details)}' - response = self._make_api_call(url, 'GET') + response = self._make_api_call(url, 'GET', timeout=timeout) if not response.ok: raise ValueError(f'Error while getting knowledge base detail: {response.status_code=} {response.text=}') return response.json() @@ -317,27 +333,30 @@ def ask_question( prompt: str = None, temperature: float = None, as_dict: bool = False, + timeout: float = 60.0, ) -> Conversation: token = identification.access_token if isinstance(identification, Identification) else identification data = {'question': question} for key, value in [('model', model), ('app_name', app_name), ('prompt', prompt), ('temperature', temperature)]: if value is not None: data[key] = value - response = self._make_api_call(self.conversation_url(token), 'POST', data) + response = self._make_api_call(self.conversation_url(token), 'POST', data, timeout=timeout) data = response.json() return data if as_dict else Conversation.from_dict(data) - def get_conversation(self, identification: IdentificationType | str | int) -> Conversation: + def get_conversation(self, identification: IdentificationType | str | int, timeout: float = 60.0) -> Conversation: token = identification.access_token if isinstance(identification, Identification) else identification - response = self._make_api_call(self.conversation_url(token), 'GET') + response = self._make_api_call(self.conversation_url(token), 'GET', timeout=timeout) return Conversation.from_dict(response.json()) - def delete_conversation(self, identification: IdentificationType | str | int) -> bool: + def delete_conversation(self, identification: IdentificationType | str | int, timeout: float = 60.0) -> bool: token = identification.access_token if isinstance(identification, Identification) else identification - self._make_api_call(self.conversation_url(token), 'DELETE') + self._make_api_call(self.conversation_url(token), 'DELETE', timeout=timeout) return True - def conversation_feedback(self, identification: IdentificationType | str | int, feedback: str | int | dict) -> bool: + def conversation_feedback( + self, identification: IdentificationType | str | int, feedback: str | int | dict, timeout: float = 60.0 + ) -> bool: token = identification.access_token if isinstance(identification, Identification) else identification - self._make_api_call(self.conversation_feedback_url(token), 'POST', {'feedback': feedback}) + self._make_api_call(self.conversation_feedback_url(token), 'POST', {'feedback': feedback}, timeout=timeout) return True diff --git a/kindwise/crop_health.py b/kindwise/crop_health.py index fea0b92..81efb9c 100644 --- a/kindwise/crop_health.py +++ b/kindwise/crop_health.py @@ -73,5 +73,6 @@ def ask_question( prompt: str = None, temperature: float = None, as_dict: bool = False, + timeout=60.0, ) -> Conversation: raise NotImplementedError('Asking questions is currently not supported by crop.health.') diff --git a/kindwise/insect.py b/kindwise/insect.py index 9304c7b..a0b7ea2 100644 --- a/kindwise/insect.py +++ b/kindwise/insect.py @@ -97,6 +97,7 @@ def identify( as_dict: bool = False, extra_get_params: str | dict[str, str] = None, extra_post_params: str | dict[str, dict[str, str]] | dict[str, str] = None, + timeout=60.0, ) -> InsectIdentification | dict: identification = super().identify( image=image, @@ -111,6 +112,7 @@ def identify( as_dict=True, extra_get_params=extra_get_params, extra_post_params=extra_post_params, + timeout=timeout, ) if as_dict: return identification @@ -124,6 +126,7 @@ def get_identification( language: str | list[str] = None, as_dict: bool = False, extra_get_params: str | dict[str, str] = None, + timeout=60.0, ) -> InsectIdentification | dict: identification = super().get_identification( token=token, @@ -131,6 +134,7 @@ def get_identification( language=language, as_dict=True, extra_get_params=extra_get_params, + timeout=timeout, ) return identification if as_dict else InsectIdentification.from_dict(identification) @@ -147,5 +151,6 @@ def ask_question( prompt: str = None, temperature: float = None, as_dict: bool = False, + timeout=60.0, ) -> Conversation: raise NotImplementedError('Asking questions is currently not supported by insect.id') diff --git a/kindwise/mushroom.py b/kindwise/mushroom.py index ed80772..a834bea 100644 --- a/kindwise/mushroom.py +++ b/kindwise/mushroom.py @@ -101,6 +101,7 @@ def identify( as_dict: bool = False, extra_get_params: str | dict[str, str] = None, extra_post_params: str | dict[str, dict[str, str]] | dict[str, str] = None, + timeout=60.0, ) -> MushroomIdentification | dict: identification = super().identify( image=image, @@ -115,6 +116,7 @@ def identify( as_dict=True, extra_get_params=extra_get_params, extra_post_params=extra_post_params, + timeout=timeout, ) if as_dict: return identification @@ -128,6 +130,7 @@ def get_identification( language: str | list[str] = None, as_dict: bool = False, extra_get_params: str | dict[str, str] = None, + timeout=60.0, ) -> MushroomIdentification | dict: identification = super().get_identification( token=token, @@ -135,6 +138,7 @@ def get_identification( language=language, as_dict=True, extra_get_params=extra_get_params, + timeout=timeout, ) return identification if as_dict else MushroomIdentification.from_dict(identification) @@ -151,5 +155,6 @@ def ask_question( prompt: str = None, temperature: float = None, as_dict: bool = False, + timeout=60.0, ) -> Conversation: raise NotImplementedError('Asking questions is currently not supported by mushroom.id') diff --git a/kindwise/plant.py b/kindwise/plant.py index e135e92..928df16 100644 --- a/kindwise/plant.py +++ b/kindwise/plant.py @@ -262,6 +262,7 @@ def identify( as_dict: bool = False, extra_get_params: str | dict[str, str] = None, extra_post_params: str | dict[str, dict[str, str]] | dict[str, str] = None, + timeout=60.0, ) -> PlantIdentification | RawPlantIdentification | HealthAssessment | dict: identification = super().identify( image=image, @@ -279,6 +280,7 @@ def identify( as_dict=True, extra_get_params=extra_get_params, extra_post_params=extra_post_params, + timeout=timeout, ) if as_dict: return identification @@ -296,6 +298,7 @@ def get_identification( language: str | list[str] = None, as_dict: bool = False, extra_get_params: str | dict[str, str] = None, + timeout=60.0, ) -> PlantIdentification | dict: identification = super().get_identification( token=token, @@ -303,6 +306,7 @@ def get_identification( language=language, as_dict=True, extra_get_params=extra_get_params, + timeout=timeout, ) # todo might be RawPlantIdentification return identification if as_dict else PlantIdentification.from_dict(identification) @@ -335,6 +339,7 @@ def health_assessment( as_dict: bool = False, extra_get_params: str | dict[str, str] = None, extra_post_params: str = None, + timeout=60.0, ) -> HealthAssessment | dict: query = self._build_query( details=details, @@ -353,7 +358,7 @@ def health_assessment( max_image_size=max_image_size, extra_post_params=extra_post_params, ) - response = self._make_api_call(url, 'POST', payload) + response = self._make_api_call(url, 'POST', payload, timeout=timeout) if not response.ok: raise ValueError(f'Error while creating a health assessment: {response.status_code=} {response.text=}') health_assessment = response.json() @@ -367,19 +372,24 @@ def get_health_assessment( full_disease_list: bool = False, as_dict: bool = False, extra_get_params: str | dict[str, str] = None, + timeout=60.0, ) -> HealthAssessment | dict: query = self._build_query( details=details, language=language, full_disease_list=full_disease_list, extra_get_params=extra_get_params ) url = f'{self.identification_url}/{token}{query}' - response = self._make_api_call(url, 'GET') + response = self._make_api_call(url, 'GET', timeout=timeout) if not response.ok: raise ValueError(f'Error while getting a health assessment: {response.status_code=} {response.text=}') health_assessment = response.json() return health_assessment if as_dict else HealthAssessment.from_dict(health_assessment) - def delete_health_assessment(self, identification: HealthAssessment | str | int) -> bool: - return self.delete_identification(identification) + def delete_health_assessment( + self, + identification: HealthAssessment | str | int, + timeout=60.0, + ) -> bool: + return self.delete_identification(identification, timeout=timeout) @property def views_path(self) -> Path: