sapiopycommons 2025.10.20a789__py3-none-any.whl → 2025.10.21a792__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of sapiopycommons might be problematic. Click here for more details.

@@ -336,9 +336,9 @@ class AgentServiceBase(ToolServiceServicer, ABC):
336
336
 
337
337
  # Instantiate the agent class.
338
338
  agent: AgentBase = registered_agents[find_agent]()
339
+ # Setup the agent with details from the request.
340
+ agent.setup(user, request, context, self.debug_mode)
339
341
  try:
340
- # Setup the agent with details from the request.
341
- agent.setup(user, request, context, self.debug_mode)
342
342
  # Validate that the provided inputs match the agent's expected inputs.
343
343
  msg: str = ""
344
344
  if len(request.input) != len(agent.input_configs):
@@ -387,14 +387,52 @@ class AgentBase(ABC):
387
387
 
388
388
  logs: list[str]
389
389
  logger: Logger
390
- verbose_logging: bool
391
-
392
- temp_data: TempFileHandler
393
-
394
- user: SapioUser
395
- request: ProcessStepRequestPbo
396
- context: ServicerContext
397
- debug_mode: bool
390
+ _verbose_logging: bool | None = None
391
+
392
+ _temp_data: TempFileHandler | None = None
393
+
394
+ _user: SapioUser | None = None
395
+ _request: ProcessStepRequestPbo | None = None
396
+ _context: ServicerContext | None = None
397
+ _debug_mode: bool | None = None
398
+
399
+ __is_setup: bool
400
+
401
+ @property
402
+ def verbose_logging(self) -> bool:
403
+ if not self.__is_setup:
404
+ raise Exception("Agent must be set up to respond to a request before accessing this property.")
405
+ return self._verbose_logging
406
+
407
+ @property
408
+ def temp_data(self) -> TempFileHandler:
409
+ if not self.__is_setup:
410
+ raise Exception("Agent must be set up to respond to a request before accessing this property.")
411
+ return self._temp_data
412
+
413
+ @property
414
+ def user(self) -> SapioUser:
415
+ if not self.__is_setup:
416
+ raise Exception("Agent must be set up to respond to a request before accessing this property.")
417
+ return self._user
418
+
419
+ @property
420
+ def request(self) -> ProcessStepRequestPbo:
421
+ if not self.__is_setup:
422
+ raise Exception("Agent must be set up to respond to a request before accessing this property.")
423
+ return self._request
424
+
425
+ @property
426
+ def context(self) -> ServicerContext:
427
+ if not self.__is_setup:
428
+ raise Exception("Agent must be set up to respond to a request before accessing this property.")
429
+ return self._context
430
+
431
+ @property
432
+ def debug_mode(self) -> bool:
433
+ if not self.__is_setup:
434
+ raise Exception("Agent must be set up to respond to a request before accessing this property.")
435
+ return self._debug_mode
398
436
 
399
437
  @classmethod
400
438
  @abstractmethod
@@ -455,12 +493,12 @@ class AgentBase(ABC):
455
493
  return None
456
494
 
457
495
  def __init__(self):
496
+ self.__is_setup = False
458
497
  self.input_configs = []
459
498
  self.input_container_types = []
460
499
  self.output_configs = []
461
500
  self.output_container_types = []
462
501
  self.config_fields = []
463
- self.temp_data = TempFileHandler()
464
502
  self.logs = []
465
503
  self.logger = logging.getLogger(f"AgentBase.{self.name()}")
466
504
  ensure_logger_initialized(self.logger)
@@ -477,11 +515,13 @@ class AgentBase(ABC):
477
515
  :param debug_mode: If true, the agent should run in debug mode, providing additional logging and not cleaning
478
516
  up temporary files.
479
517
  """
480
- self.user = user
481
- self.request = request
482
- self.context = context
483
- self.verbose_logging = request.verbose_logging
484
- self.debug_mode = debug_mode
518
+ self.__is_setup = True
519
+ self._user = user
520
+ self._request = request
521
+ self._context = context
522
+ self._verbose_logging = request.verbose_logging
523
+ self._debug_mode = debug_mode
524
+ self._temp_data = TempFileHandler()
485
525
 
486
526
  def add_input(self, container_type: ContainerType, content_type: str, display_name: str, description: str,
487
527
  structure_example: str | bytes | None = None, validation: str | None = None,
@@ -939,35 +979,32 @@ class AgentBase(ABC):
939
979
  """
940
980
  pass
941
981
 
942
- def get_credentials(self, category: str = None, host: str = None) -> ExternalCredentials:
982
+ def get_credentials(self, name: str | None = None, category: str | None = None) -> ExternalCredentials:
943
983
  """
944
984
  Get credentials for the given category and host.
945
985
 
986
+ :param name: The host name of the credentials to retrieve.
946
987
  :param category: The category of the credentials to retrieve.
947
- :param host: The host for which to retrieve the credentials.
948
988
  :return: An ExternalCredentials object containing the credentials for the given category and host.
949
989
  """
990
+ if not self.__is_setup:
991
+ raise Exception("Cannot call this function before the agent has been set up to respond to a request.")
950
992
  # Remove leading/trailing whitespace
993
+ name = name.strip() if name else None
951
994
  category = category.strip() if category else None
952
- host = host.strip() if host else None
953
995
 
954
996
  matching_creds: list[ExternalCredentialsPbo] = []
955
997
  for cred in self.request.external_credential:
956
998
  # Do case insensitive comparison
999
+ if name and cred.display_name.lower != name.lower():
1000
+ continue
957
1001
  if category and cred.category.lower() != category.lower():
958
1002
  continue
959
- if host:
960
- # Parse the URL to get the host and compare
961
- from urllib.parse import urlparse
962
- parsed_url = urlparse(cred.url)
963
- if parsed_url.hostname is None or parsed_url.hostname.lower() != host.lower():
964
- continue
965
-
966
1003
  matching_creds.append(cred)
967
1004
  if len(matching_creds) == 0:
968
- raise ValueError(f"No credentials found for category '{category}' and host '{host}'.")
1005
+ raise ValueError(f"No credentials found for name '{name}' and category '{category}'.")
969
1006
  if len(matching_creds) > 1:
970
- raise ValueError(f"Multiple credentials found for category '{category}' and host '{host}'.")
1007
+ raise ValueError(f"Multiple credentials found for name '{name}' and category '{category}'.")
971
1008
 
972
1009
  return ExternalCredentials.from_pbo(matching_creds[0])
973
1010
 
@@ -978,6 +1015,8 @@ class AgentBase(ABC):
978
1015
  :param value: The value of the credentials config field.
979
1016
  :return: An ExternalCredentials object containing the credentials.
980
1017
  """
1018
+ if not self.__is_setup:
1019
+ raise Exception("Cannot call this function before the agent has been set up to respond to a request.")
981
1020
  # Values should be of the format "Name (Identifier)"
982
1021
  match = re.match(r"^(.*) \((.*)\)$", value)
983
1022
  if not match:
@@ -1004,23 +1043,34 @@ class AgentBase(ABC):
1004
1043
  """
1005
1044
  try:
1006
1045
  self.log_info(f"Running subprocess with command: {' '.join(args)}")
1007
- return subprocess.run(args, check=True, capture_output=True, text=True, cwd=cwd, **kwargs)
1046
+ p: CompletedProcess[str] = subprocess.run(args, check=True, capture_output=True, text=True, cwd=cwd,
1047
+ **kwargs)
1048
+ if p.stdout:
1049
+ self.log_info(f"STDOUT: {p.stdout}")
1050
+ if p.stderr:
1051
+ self.log_info(f"STDERR: {p.stderr}")
1052
+ return p
1008
1053
  except subprocess.CalledProcessError as e:
1009
1054
  self.log_error(f"Error running subprocess. Return code: {e.returncode}")
1010
- self.log_error(f"STDOUT: {e.stdout}")
1011
- self.log_error(f"STDERR: {e.stderr}")
1055
+ if e.stdout:
1056
+ self.log_error(f"STDOUT: {e.stdout}")
1057
+ if e.stderr:
1058
+ self.log_error(f"STDERR: {e.stderr}")
1012
1059
  raise
1013
1060
 
1014
1061
  def log_info(self, message: str) -> None:
1015
1062
  """
1016
1063
  Log an info message for this agent. If verbose logging is enabled, this message will be included in the logs
1017
1064
  returned to the caller. Empty/None inputs will not be logged.
1065
+
1066
+ Logging info can be done during initialization, but those logs will not be returned to the caller. Other
1067
+ log calls will be returned to the caller, even if done during initialization.
1018
1068
 
1019
1069
  :param message: The message to log.
1020
1070
  """
1021
1071
  if not message:
1022
1072
  return
1023
- if self.verbose_logging:
1073
+ if self.__is_setup and self.verbose_logging:
1024
1074
  self.logs.append(f"INFO: {self.name()}: {message}")
1025
1075
  self.logger.info(message)
1026
1076
 
@@ -1068,6 +1118,8 @@ class AgentBase(ABC):
1068
1118
  :param index: The index of the input to check. Defaults to 0. Used for agents that accept multiple inputs.
1069
1119
  :return: True if the input is marked as partial, False otherwise.
1070
1120
  """
1121
+ if not self.__is_setup:
1122
+ raise Exception("Cannot call this function before the agent has been set up to respond to a request.")
1071
1123
  return self.request.input[index].is_partial
1072
1124
 
1073
1125
  def get_input_name(self, index: int = 0) -> str | None:
@@ -1077,6 +1129,8 @@ class AgentBase(ABC):
1077
1129
  :param index: The index of the input to parse. Defaults to 0. Used for agents that accept multiple inputs.
1078
1130
  :return: The name of the input from the request object, or None if no name is set.
1079
1131
  """
1132
+ if not self.__is_setup:
1133
+ raise Exception("Cannot call this function before the agent has been set up to respond to a request.")
1080
1134
  return self.request.input[index].item_container.container_name
1081
1135
 
1082
1136
  def get_input_content_type(self, index: int = 0) -> ContentTypePbo:
@@ -1086,6 +1140,8 @@ class AgentBase(ABC):
1086
1140
  :param index: The index of the input to parse. Defaults to 0. Used for agents that accept multiple inputs.
1087
1141
  :return: The content type of the input from the request object.
1088
1142
  """
1143
+ if not self.__is_setup:
1144
+ raise Exception("Cannot call this function before the agent has been set up to respond to a request.")
1089
1145
  return self.request.input[index].item_container.content_type
1090
1146
 
1091
1147
  def get_input_binary(self, index: int = 0) -> list[bytes]:
@@ -1095,6 +1151,8 @@ class AgentBase(ABC):
1095
1151
  :param index: The index of the input to parse. Defaults to 0. Used for agents that accept multiple inputs.
1096
1152
  :return: The binary data from the request object.
1097
1153
  """
1154
+ if not self.__is_setup:
1155
+ raise Exception("Cannot call this function before the agent has been set up to respond to a request.")
1098
1156
  container: StepItemContainerPbo = self.request.input[index].item_container
1099
1157
  if not container.HasField("binary_container"):
1100
1158
  raise Exception(f"Input {index} does not contain a binary container.")
@@ -1109,6 +1167,8 @@ class AgentBase(ABC):
1109
1167
  the column names, and the data rows are a list of dictionaries where each dictionary represents a row in the
1110
1168
  CSV with the column names as keys and the corresponding values as strings.
1111
1169
  """
1170
+ if not self.__is_setup:
1171
+ raise Exception("Cannot call this function before the agent has been set up to respond to a request.")
1112
1172
  container: StepItemContainerPbo = self.request.input[index].item_container
1113
1173
  if not container.HasField("csv_container"):
1114
1174
  raise Exception(f"Input {index} does not contain a CSV container.")
@@ -1128,6 +1188,8 @@ class AgentBase(ABC):
1128
1188
  :param index: The index of the input to parse. Defaults to 0. Used for agents that accept multiple inputs.
1129
1189
  :return: A list of parsed JSON objects, which are represented as dictionaries.
1130
1190
  """
1191
+ if not self.__is_setup:
1192
+ raise Exception("Cannot call this function before the agent has been set up to respond to a request.")
1131
1193
  container: StepItemContainerPbo = self.request.input[index].item_container
1132
1194
  if not container.HasField("json_container"):
1133
1195
  raise Exception(f"Input {index} does not contain a JSON container.")
@@ -1148,6 +1210,8 @@ class AgentBase(ABC):
1148
1210
  :param index: The index of the input to parse. Defaults to 0. Used for agents that accept multiple inputs.
1149
1211
  :return: A list of text data as strings.
1150
1212
  """
1213
+ if not self.__is_setup:
1214
+ raise Exception("Cannot call this function before the agent has been set up to respond to a request.")
1151
1215
  container: StepItemContainerPbo = self.request.input[index].item_container
1152
1216
  if not container.HasField("text_container"):
1153
1217
  raise Exception(f"Input {index} does not contain a text container.")
@@ -1175,6 +1239,8 @@ class AgentBase(ABC):
1175
1239
  (bool for boolean fields, float for double fields, int for short, integer, long, and enum fields, and
1176
1240
  string for everything else).
1177
1241
  """
1242
+ if not self.__is_setup:
1243
+ raise Exception("Cannot call this function before the agent has been set up to respond to a request.")
1178
1244
  config_fields: dict[str, Any] = {}
1179
1245
  raw_configs: Mapping[str, FieldValuePbo] = self.request.config_field_values
1180
1246
  for field_name, field_def in self.get_config_defs().items():
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: sapiopycommons
3
- Version: 2025.10.20a789
3
+ Version: 2025.10.21a792
4
4
  Summary: Official Sapio Python API Utilities Package
5
5
  Project-URL: Homepage, https://github.com/sapiosciences
6
6
  Author-email: Jonathan Steck <jsteck@sapiosciences.com>, Yechen Qiao <yqiao@sapiosciences.com>
@@ -1,6 +1,6 @@
1
1
  sapiopycommons/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  sapiopycommons/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- sapiopycommons/ai/agent_service_base.py,sha256=eTJunFQoxLc0risWiQIkQK946XpHzSFO38NzzRvOn9Q,60325
3
+ sapiopycommons/ai/agent_service_base.py,sha256=s3OY4UdzxBgIvT5PPbnq2MQ_n_3ihnPor4sJZDDo5NQ,63661
4
4
  sapiopycommons/ai/converter_service_base.py,sha256=HiUXmwqv1STgyQeF9_eTFXzjIFXp5-NJ7sEhMpV3aAU,6351
5
5
  sapiopycommons/ai/external_credentials.py,sha256=ki_xIH4J843b_sSwEa8YHr8vW9erVv-jowZJXSgPQs8,4347
6
6
  sapiopycommons/ai/protobuf_utils.py,sha256=cBjbxoFAwU02kNUxEce95WnMU2CMuDD-qFaeWgvQJMQ,24599
@@ -106,7 +106,7 @@ sapiopycommons/webhook/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3
106
106
  sapiopycommons/webhook/webhook_context.py,sha256=D793uLsb1691SalaPnBUk3rOSxn_hYLhdvkaIxjNXss,1909
107
107
  sapiopycommons/webhook/webhook_handlers.py,sha256=7o_wXOruhT9auNh8OfhJAh4WhhiPKij67FMBSpGPICc,39939
108
108
  sapiopycommons/webhook/webservice_handlers.py,sha256=cvW6Mk_110BzYqkbk63Kg7jWrltBCDALOlkJRu8h4VQ,14300
109
- sapiopycommons-2025.10.20a789.dist-info/METADATA,sha256=LW1ECHFl-cG6BPslNdeFfIGKFHCxPgtivYUg9mqsnSQ,3143
110
- sapiopycommons-2025.10.20a789.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
111
- sapiopycommons-2025.10.20a789.dist-info/licenses/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
112
- sapiopycommons-2025.10.20a789.dist-info/RECORD,,
109
+ sapiopycommons-2025.10.21a792.dist-info/METADATA,sha256=FBXgjKxmW_SKUAbzNUgTj5J-1h5PQMRXM6ek8hxyCq0,3143
110
+ sapiopycommons-2025.10.21a792.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
111
+ sapiopycommons-2025.10.21a792.dist-info/licenses/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
112
+ sapiopycommons-2025.10.21a792.dist-info/RECORD,,