pumpwood_communication.microservice_abc.simple
Facilitate communication with Pumpowood backend.
This packages facilitates the communication with end-points with Pumpwood pattern and helps with authentication.
Source-code at Github:
https://github.com/Murabei-OpenSource-Codes/pumpwood-communication
1""" 2Facilitate communication with Pumpowood backend. 3 4This packages facilitates the communication with end-points with Pumpwood 5pattern and helps with authentication. 6 7Source-code at Github:<br> 8https://github.com/Murabei-OpenSource-Codes/pumpwood-communication 9""" 10 11__docformat__ = "google" 12 13 14from .batch import ABCSimpleBatchMicroservice 15from .retrieve import ABCSimpleRetriveMicroservice 16from .delete import ABCSimpleDeleteMicroservice 17from .save import ABCSimpleSaveMicroservice 18from .list import ABCSimpleListMicroservice 19from .dimensions import ABCSimpleDimensionMicroservice 20from .action import ABCSimpleActionMicroservice 21from .info import ABCSimpleInfoMicroservice 22 23 24__all__ = [ 25 ABCSimpleBatchMicroservice, ABCSimpleRetriveMicroservice, 26 ABCSimpleDeleteMicroservice, ABCSimpleSaveMicroservice, 27 ABCSimpleListMicroservice, ABCSimpleDimensionMicroservice, 28 ABCSimpleActionMicroservice, ABCSimpleInfoMicroservice]
21class ABCSimpleBatchMicroservice(ABC, PumpWoodMicroServiceBase): 22 """Abstract class for batch end-points.""" 23 24 @staticmethod 25 def _build_aggregate_url(model_class: str): 26 return "rest/%s/aggregate/" % (model_class.lower(),) 27 28 def aggregate(self, model_class: str, group_by: List[str] | str, agg: dict, 29 filter_dict: None | dict = None, exclude_dict: dict = None, 30 order_by: List[str] = None, auth_header: dict = None, 31 limit: int = None, as_dataframe: bool = False, 32 show_deleted: bool = False, 33 ) -> pd.DataFrame: 34 """Save a list of objects with one request. 35 36 Avaiable aggregate functions: 37 - **sum:** Sum. 38 - **mean:** Mean. 39 - **count:** Count. 40 - **min:** Minimum. 41 - **max:** Maximum. 42 - **stddev_pop:** Population standard deviation. 43 - **stddev_samp:** Sample standard deviation. 44 - **var_pop:** Population variance. 45 - **var_samp:** Sample variance. 46 - **std:** Sample standard deviation. 47 - **var:** Sample variance. 48 49 Args: 50 model_class (str): 51 Model class of the end-point that will be aggregated. 52 group_by (List[str] | str): 53 List of the fields that will be used on aggregation as 54 group by. If a string is passed as argument, it will be 55 considered the sigle group_by column. 56 agg (dict): 57 A dictionary with dictionary items as `field` and `function` 58 specifing the field that will be aggregated using a function. 59 60 The dictinary keys will be used to return the results as 61 columns. 62 show_deleted (bool): 63 If deleted data should be returned. 64 filter_dict (dict): 65 Filter that will be applied before the aggregation. 66 exclude_dict (dict): 67 Exclude clause that will be applied before the aggregation. 68 order_by (list): 69 Ordenation acording to grouping elements. It can be used 70 fields created as keys of agg dictinary. 71 auth_header (dict): 72 Authentication header used to impersonation of user. 73 limit (int): 74 Limit number of returned row at aggregation query. 75 as_dataframe (bool): 76 If result should be returned as a dataframe. The columns 77 will be set to match the group_by and agg arguments, this 78 way empty dataframe with respect the columns. 79 80 Returns: 81 Return a DataFrame with aggregation results. 82 83 Example: 84 ``` 85 microservice.aggregate( 86 model_class="ToLoadCalendar", 87 group_by=["calendar_id"], 88 agg={ 89 "n": {"field": "id", "function": "count"}, 90 "mean": {"field": "value", "function": "mean" 91 }}) 92 ``` 93 """ 94 filter_dict = {} if filter_dict is None else filter_dict 95 exclude_dict = {} if exclude_dict is None else exclude_dict 96 order_by = [] if order_by is None else order_by 97 98 # If group_by is a string, convert to a list with this string 99 group_by = [group_by] if isinstance(group_by, str) else group_by 100 if not isinstance(group_by, (list, tuple, set)): 101 error_msg = "Argument `group_by` must be list, tuple, set or str." 102 raise TypeError(error_msg) 103 if not isinstance(agg, (dict)): 104 error_msg = "Argument `agg` must be dict." 105 raise TypeError(error_msg) 106 107 url_str = self._build_aggregate_url(model_class=model_class) 108 data = { 109 'agg': agg, 'group_by': group_by, 'filter_dict': filter_dict, 110 'exclude_dict': exclude_dict, 'order_by': order_by, 111 'limit': limit, 'show_deleted': show_deleted} 112 return_data = self.request_post( 113 url=url_str, data=data, auth_header=auth_header) 114 if not as_dataframe: 115 return return_data 116 else: 117 # Return the results as a dataframe using the group_by columns 118 # and the columns created at aggregation to ensure that 119 # even empty results would have the correct columns 120 return_columns = group_by + list(agg.keys()) 121 return pd.DataFrame(return_data, columns=return_columns) 122 123 @staticmethod 124 def _build_pivot_url(model_class): 125 return "rest/%s/pivot/" % (model_class.lower(), ) 126 127 def pivot(self, model_class: str, columns: None | List[str] = None, 128 format: str = 'list', filter_dict: dict = None, 129 exclude_dict: dict = None, order_by: List[str] = None, 130 variables: List[str] = None, fields: List[str] = None, 131 show_deleted: bool = False, 132 add_pk_column: bool = False, auth_header: dict = None, 133 as_dataframe: bool = False 134 ) -> Union[List[dict], Dict[str, list], pd.DataFrame]: 135 """Pivot object data acording to columns specified. 136 137 Pivoting per-se is not usually used, beeing the name of the function 138 a legacy. Normality data transformation is done at the client level. 139 140 Args: 141 model_class (str): 142 Model class to check search parameters. 143 columns (List[str]): 144 List of fields to be used as columns when pivoting the data. 145 format (str): 146 Format to be used to convert pandas.DataFrame to 147 dictionary, must be in ['dict','list','series', 148 'split', 'records','index']. 149 filter_dict (dict): 150 Same as list function. 151 exclude_dict (dict): 152 Same as list function. 153 order_by (List[str]): 154 Same as list function. 155 fields (List[str]): 156 List of the fields to be returned, if None, the default 157 variables will be returned. Same as fields on list functions. 158 variables (List[str]): 159 List of the fields to be returned, if None, the default 160 variables will be returned. Same as fields on list functions. 161 **DEPRECTED** use fields. 162 show_deleted (bool): 163 Fields with deleted column will have objects with deleted=True 164 omited from results. show_deleted=True will return this 165 information. 166 add_pk_column (bool): 167 If add pk values of the objects at pivot results. Adding 168 pk key on pivot end-points won't be possible to pivot since 169 pk is unique for each entry. 170 auth_header (dict): 171 Auth header to substitute the microservice original 172 at the request (user impersonation). 173 as_dataframe (bool): 174 If results should be returned as a dataframe. 175 176 Returns: 177 Return a list or a dictinary depending on the format set on 178 format parameter. 179 180 Raises: 181 PumpWoodException: 182 'Columns must be a list of elements.'. Indicates that the list 183 argument was not a list. 184 PumpWoodException: 185 'Column chosen as pivot is not at model variables'. Indicates 186 that columns that were set to pivot are not present on model 187 variables. 188 PumpWoodException: 189 "Format must be in ['dict','list','series','split', 190 'records','index']". Indicates that format set as paramenter 191 is not implemented. 192 PumpWoodException: 193 "Can not add pk column and pivot information". If 194 add_pk_column is True (results will have the pk column), it is 195 not possible to pivot the information (pk is an unique value 196 for each object, there is no reason to pivot it). 197 PumpWoodException: 198 "'value' column not at melted data, it is not possible 199 to pivot dataframe.". Indicates that data does not have a value 200 column, it must have it to populate pivoted table. 201 """ 202 # Deprect variables argument 203 if variables is not None: 204 msg = ( 205 "Deprecation Warning: pivot variable is deprected, " 206 "you should use fields instead") 207 logger.warning(msg) 208 fields = fields if fields is not None else variables 209 210 filter_dict = {} if filter_dict is None else filter_dict 211 exclude_dict = {} if exclude_dict is None else exclude_dict 212 order_by = [] if order_by is None else order_by 213 columns = [] if columns is None else columns 214 215 url_str = self._build_pivot_url(model_class) 216 post_data = { 217 'columns': columns, 'format': format, 218 'filter_dict': filter_dict, 'exclude_dict': exclude_dict, 219 'order_by': order_by, "variables": variables, 220 "show_deleted": show_deleted, "add_pk_column": add_pk_column} 221 pivot_results = self.request_post( 222 url=url_str, data=post_data, auth_header=auth_header) 223 224 if not add_pk_column: 225 if as_dataframe: 226 # If passed, set the fields as columns to ensure empty 227 # dataframes to have the correct fields 228 return pd.DataFrame(pivot_results, columns=fields) 229 else: 230 return pivot_results 231 else: 232 # If passed, set the fields as columns to ensure empty 233 # dataframes to have the correct fields 234 pd_pivot_results = pd.DataFrame(pivot_results, columns=fields) 235 if len(pd_pivot_results) != 0: 236 fill_options = self.fill_options( 237 model_class=model_class, auth_header=auth_header) 238 primary_keys_data = fill_options["pk"] 239 # Allow legacy pk definition and actual that uses extra_info 240 primary_keys = primary_keys_data\ 241 .get('extra_info', {})\ 242 .get('columns', primary_keys_data['column']) 243 pd_pivot_results["pk"] = pd_pivot_results[primary_keys].apply( 244 CompositePkBase64Converter.dump, 245 primary_keys=primary_keys, axis=1) 246 if as_dataframe: 247 return pd_pivot_results 248 else: 249 return pd_pivot_results.to_dict(format) 250 251 def _flat_list_by_chunks_helper(self, args): 252 try: 253 # Unpacking arguments 254 model_class = args["model_class"] 255 filter_dict = args["filter_dict"] 256 exclude_dict = args["exclude_dict"] 257 fields = args["fields"] 258 show_deleted = args["show_deleted"] 259 auth_header = args["auth_header"] 260 chunk_size = args["chunk_size"] 261 262 temp_filter_dict = copy.deepcopy(filter_dict) 263 url_str = self._build_pivot_url(model_class) 264 max_pk = 0 265 266 # Fetch data until an empty result is returned 267 list_dataframes = [] 268 while True: 269 sys.stdout.write(".") 270 sys.stdout.flush() 271 temp_filter_dict["id__gt"] = max_pk 272 post_data = { 273 'format': 'list', 274 'filter_dict': temp_filter_dict, 275 'exclude_dict': exclude_dict, 276 'order_by': ["id"], "variables": fields, 277 "show_deleted": show_deleted, 278 "limit": chunk_size, 279 "add_pk_column": True} 280 temp_dateframe = pd.DataFrame(self.request_post( 281 url=url_str, data=post_data, auth_header=auth_header)) 282 283 # Break if results are less than chunk size, so no more results 284 # are avaiable 285 if len(temp_dateframe) < chunk_size: 286 list_dataframes.append(temp_dateframe) 287 break 288 289 max_pk = int(temp_dateframe["id"].max()) 290 list_dataframes.append(temp_dateframe) 291 292 if len(list_dataframes) == 0: 293 return pd.DataFrame() 294 else: 295 return pd.concat(list_dataframes) 296 except Exception as e: 297 raise Exception("Exception at flat_list_by_chunks:", str(e)) 298 299 def flat_list_by_chunks(self, model_class: str, filter_dict: dict = None, 300 exclude_dict: dict = None, 301 fields: List[str] = None, 302 show_deleted: bool = False, 303 auth_header: dict = None, 304 chunk_size: int = 1000000, 305 n_parallel: int = None, 306 create_composite_pk: bool = False, 307 start_date: str = None, 308 end_date: str = None, 309 time_column: str = 'time') -> pd.DataFrame: 310 """Incrementally fetch data from pivot end-point. 311 312 Fetch data from pivot end-point paginating by id of chunk_size length. 313 314 If table is partitioned it will split the query acording to partition 315 to facilitate query at the database. 316 317 If start_date and end_date are set, also breaks the query by month 318 retrieving each month data in parallel. 319 320 Args: 321 model_class (str): 322 Model class to be pivoted. 323 filter_dict (dict): 324 Dictionary to to be used in objects.filter argument 325 (Same as list end-point). 326 exclude_dict (dict): 327 Dictionary to to be used in objects.exclude argument 328 (Same as list end-point). 329 fields (List[str] | None): 330 List of the variables to be returned, 331 if None, the default variables will be returned. 332 If fields is set, dataframe will return that columns 333 even if data is empty. 334 start_date (datetime | str): 335 Set a begin date for the query. If begin and end date are 336 set, query will be splited with chucks by month that will be 337 requested in parallel. 338 end_date (datetime | str): 339 Set a end date for the query. If begin and end date are 340 set, query will be splited with chucks by month that will be 341 requested in parallel. 342 show_deleted (bool): 343 If deleted data should be returned. 344 auth_header (dict): 345 Auth header to substitute the microservice original 346 at the request (user impersonation). 347 chunk_size (int): 348 Limit of data to fetch per call. 349 n_parallel (int): 350 Number of parallel process to perform. 351 create_composite_pk (bool): 352 If true and table has a composite pk, it will create pk 353 value based on the hash on the json serialized dictionary 354 of the components of the primary key. 355 time_column (str): 356 Column that will be considered on the partitioning of data 357 fetch. 358 359 Returns: 360 Returns a dataframe with all information fetched. 361 362 Raises: 363 No particular raise. 364 """ 365 # Set empty dictionary for dictionary arguments 366 filter_dict = {} if filter_dict is None else filter_dict 367 exclude_dict = {} if exclude_dict is None else exclude_dict 368 original_fields = None if fields is None else fields 369 370 if n_parallel is None: 371 n_parallel = PUMPWOOD_COMUNICATION__N_PARALLEL 372 373 fill_options = self.fill_options( 374 model_class=model_class, auth_header=auth_header) 375 376 # Retrieve PK infomration about primary_key and partitions 377 primary_keys = fill_options["pk"]["extra_info"]['columns'] 378 # partition = fill_options["pk"]["extra_info"]['partition'] 379 380 # Add all primary keys fields to query if create_composite_pk is 381 # set true 382 if create_composite_pk and fields is not None: 383 fields = list(set(fields) | set(primary_keys)) 384 385 # Create a list of month and include start and end dates if not at 386 # the beginning of a month 387 month_sequence = None 388 if (start_date is not None) and (end_date is not None): 389 month_sequence = AuxFlatListByChunks.build_month_partitions( 390 start_date=start_date, 391 end_date=end_date) 392 elif (start_date is not None) or (end_date is not None): 393 # To create the partitions is necessary to have both start and end 394 # date. 395 msg = ( 396 "To break query in chunks using start_date and end_date " 397 "both must be set.\n- start_date: {start_date}\n" 398 "- end_date: {end_date}") 399 raise PumpWoodException( 400 message=msg, payload={ 401 "start_date": start_date, 402 "end_date": end_date}) 403 404 pool_arguments = AuxFlatListByChunks.build_query_partitions( 405 model_class=model_class, time_partitions=month_sequence, 406 filter_dict=filter_dict, exclude_dict=exclude_dict, fields=fields, 407 time_column=time_column, show_deleted=show_deleted, 408 auth_header=auth_header, chunk_size=chunk_size) 409 410 # Perform parallel calls to backend each chucked by chunk_size 411 print("## Starting parallel flat list: %s" % len(pool_arguments)) 412 resp_df = None 413 try: 414 with Pool(n_parallel) as p: 415 results = p.map( 416 self._flat_list_by_chunks_helper, 417 pool_arguments) 418 if len(results) != 0: 419 resp_df = pd.concat(results)\ 420 .reset_index(drop=True) 421 else: 422 resp_df = pd.DataFrame(columns=fields) 423 except Exception as e: 424 PumpWoodException(message=str(e)) 425 print("\n## Finished parallel flat list: %s" % len(pool_arguments)) 426 427 # Add the primary key as a column to the dataframe 428 resp_df = AuxFlatListByChunks.add_pk_column( 429 create_composite_pk=create_composite_pk, 430 primary_key_list=primary_keys, 431 data=resp_df) 432 433 # Limit the return fields 434 if original_fields is not None: 435 if create_composite_pk: 436 return pd.DataFrame( 437 resp_df, columns=['pk'] + original_fields) 438 else: 439 return pd.DataFrame( 440 resp_df, columns=original_fields) 441 else: 442 return resp_df 443 444 def get_pks_from_unique_field(self, model_class: str, field: str, 445 values: List[Any]) -> pd.DataFrame: 446 """Get pk using unique fields values. 447 448 Use unique field values to retrieve pk of the objects. This end-point 449 is usefull for retrieving pks of the objects associated with unique 450 fields such as `description` (unique on most model of pumpwood). 451 452 ```python 453 # Using description to fetch pks from objects 454 data: pd.DataFrame = [data with unique description but without pk] 455 data['attribute_id'] = microservice.get_pks_from_unique_field( 456 model_class="DescriptionAttribute", 457 field="description", values=data['attribute'])['pk'] 458 459 # Using a dimension key to fetch pk of the objects, dimension 460 # key must be unique 461 data['georea_id'] = microservice.get_pks_from_unique_field( 462 model_class="DescriptionGeoarea", field="dimension->city", 463 values=data['city'])['pk'] 464 ``` 465 466 Args: 467 model_class: 468 Model class of the objects. 469 field: 470 Unique field to fetch pk. It is possible to use dimension keys 471 as unique field, for that use `dimension->[key]` notation. 472 values: 473 List of the unique fields used to fetch primary keys. 474 475 Return: 476 Return a dataframe in same order as values with columns: 477 - **pk**: Correspondent primary key of the unique value. 478 - **[field]**: Column with same name of field argument, 479 correspondent to pk. 480 481 Raises: 482 PumpWoodQueryException: 483 Raises if field is not found on the model and it is note 484 associated with a dimension tag. 485 PumpWoodQueryException: 486 Raises if `field` does not have a unique restriction on 487 database. Dimension keys does not check for uniqueness on 488 database, be carefull not to duplicate the lines. 489 """ 490 is_dimension_tag = 'dimensions->' in field 491 if not is_dimension_tag: 492 fill_options = self.fill_options(model_class=model_class) 493 field_details = fill_options.get(field) 494 if field_details is None: 495 msg = ( 496 "Field is not a dimension tag and not found on model " 497 "fields. Field [{field}]") 498 raise PumpWoodQueryException( 499 message=msg, payload={"field": field}) 500 501 is_unique_field = field_details.get("unique", False) 502 if not is_unique_field: 503 msg = "Field [{field}] to get pk from is not unique" 504 raise PumpWoodQueryException( 505 message=msg, payload={"field": field}) 506 507 filter_dict = {field + "__in": list(set(values))} 508 pk_map = None 509 if not is_dimension_tag: 510 list_results = pd.DataFrame(self.list_without_pag( 511 model_class=model_class, filter_dict=filter_dict, 512 fields=["pk", field]), columns=["pk", field]) 513 pk_map = list_results.set_index(field)["pk"] 514 515 # If is dimension tag, fetch dimension and unpack it 516 else: 517 dimension_tag = field.split("->")[1] 518 list_results = pd.DataFrame(self.list_without_pag( 519 model_class=model_class, filter_dict=filter_dict, 520 fields=["pk", "dimensions"])) 521 pk_map = {} 522 if len(list_results) != 0: 523 pk_map = list_results\ 524 .pipe(unpack_dict_columns, columns=["dimensions"])\ 525 .set_index(dimension_tag)["pk"] 526 527 values_series = pd.Series(values) 528 return pd.DataFrame({ 529 "pk": values_series.map(pk_map).to_numpy(), 530 field: values_series 531 })
Abstract class for batch end-points.
28 def aggregate(self, model_class: str, group_by: List[str] | str, agg: dict, 29 filter_dict: None | dict = None, exclude_dict: dict = None, 30 order_by: List[str] = None, auth_header: dict = None, 31 limit: int = None, as_dataframe: bool = False, 32 show_deleted: bool = False, 33 ) -> pd.DataFrame: 34 """Save a list of objects with one request. 35 36 Avaiable aggregate functions: 37 - **sum:** Sum. 38 - **mean:** Mean. 39 - **count:** Count. 40 - **min:** Minimum. 41 - **max:** Maximum. 42 - **stddev_pop:** Population standard deviation. 43 - **stddev_samp:** Sample standard deviation. 44 - **var_pop:** Population variance. 45 - **var_samp:** Sample variance. 46 - **std:** Sample standard deviation. 47 - **var:** Sample variance. 48 49 Args: 50 model_class (str): 51 Model class of the end-point that will be aggregated. 52 group_by (List[str] | str): 53 List of the fields that will be used on aggregation as 54 group by. If a string is passed as argument, it will be 55 considered the sigle group_by column. 56 agg (dict): 57 A dictionary with dictionary items as `field` and `function` 58 specifing the field that will be aggregated using a function. 59 60 The dictinary keys will be used to return the results as 61 columns. 62 show_deleted (bool): 63 If deleted data should be returned. 64 filter_dict (dict): 65 Filter that will be applied before the aggregation. 66 exclude_dict (dict): 67 Exclude clause that will be applied before the aggregation. 68 order_by (list): 69 Ordenation acording to grouping elements. It can be used 70 fields created as keys of agg dictinary. 71 auth_header (dict): 72 Authentication header used to impersonation of user. 73 limit (int): 74 Limit number of returned row at aggregation query. 75 as_dataframe (bool): 76 If result should be returned as a dataframe. The columns 77 will be set to match the group_by and agg arguments, this 78 way empty dataframe with respect the columns. 79 80 Returns: 81 Return a DataFrame with aggregation results. 82 83 Example: 84 ``` 85 microservice.aggregate( 86 model_class="ToLoadCalendar", 87 group_by=["calendar_id"], 88 agg={ 89 "n": {"field": "id", "function": "count"}, 90 "mean": {"field": "value", "function": "mean" 91 }}) 92 ``` 93 """ 94 filter_dict = {} if filter_dict is None else filter_dict 95 exclude_dict = {} if exclude_dict is None else exclude_dict 96 order_by = [] if order_by is None else order_by 97 98 # If group_by is a string, convert to a list with this string 99 group_by = [group_by] if isinstance(group_by, str) else group_by 100 if not isinstance(group_by, (list, tuple, set)): 101 error_msg = "Argument `group_by` must be list, tuple, set or str." 102 raise TypeError(error_msg) 103 if not isinstance(agg, (dict)): 104 error_msg = "Argument `agg` must be dict." 105 raise TypeError(error_msg) 106 107 url_str = self._build_aggregate_url(model_class=model_class) 108 data = { 109 'agg': agg, 'group_by': group_by, 'filter_dict': filter_dict, 110 'exclude_dict': exclude_dict, 'order_by': order_by, 111 'limit': limit, 'show_deleted': show_deleted} 112 return_data = self.request_post( 113 url=url_str, data=data, auth_header=auth_header) 114 if not as_dataframe: 115 return return_data 116 else: 117 # Return the results as a dataframe using the group_by columns 118 # and the columns created at aggregation to ensure that 119 # even empty results would have the correct columns 120 return_columns = group_by + list(agg.keys()) 121 return pd.DataFrame(return_data, columns=return_columns)
Save a list of objects with one request.
Avaiable aggregate functions:
- sum: Sum.
- mean: Mean.
- count: Count.
- min: Minimum.
- max: Maximum.
- stddev_pop: Population standard deviation.
- stddev_samp: Sample standard deviation.
- var_pop: Population variance.
- var_samp: Sample variance.
- std: Sample standard deviation.
- var: Sample variance.
Arguments:
- model_class (str): Model class of the end-point that will be aggregated.
- group_by (List[str] | str): List of the fields that will be used on aggregation as group by. If a string is passed as argument, it will be considered the sigle group_by column.
agg (dict): A dictionary with dictionary items as
fieldandfunctionspecifing the field that will be aggregated using a function.The dictinary keys will be used to return the results as columns.
- show_deleted (bool): If deleted data should be returned.
- filter_dict (dict): Filter that will be applied before the aggregation.
- exclude_dict (dict): Exclude clause that will be applied before the aggregation.
- order_by (list): Ordenation acording to grouping elements. It can be used fields created as keys of agg dictinary.
- auth_header (dict): Authentication header used to impersonation of user.
- limit (int): Limit number of returned row at aggregation query.
- as_dataframe (bool): If result should be returned as a dataframe. The columns will be set to match the group_by and agg arguments, this way empty dataframe with respect the columns.
Returns:
Return a DataFrame with aggregation results.
Example:
microservice.aggregate( model_class="ToLoadCalendar", group_by=["calendar_id"], agg={ "n": {"field": "id", "function": "count"}, "mean": {"field": "value", "function": "mean" }})
127 def pivot(self, model_class: str, columns: None | List[str] = None, 128 format: str = 'list', filter_dict: dict = None, 129 exclude_dict: dict = None, order_by: List[str] = None, 130 variables: List[str] = None, fields: List[str] = None, 131 show_deleted: bool = False, 132 add_pk_column: bool = False, auth_header: dict = None, 133 as_dataframe: bool = False 134 ) -> Union[List[dict], Dict[str, list], pd.DataFrame]: 135 """Pivot object data acording to columns specified. 136 137 Pivoting per-se is not usually used, beeing the name of the function 138 a legacy. Normality data transformation is done at the client level. 139 140 Args: 141 model_class (str): 142 Model class to check search parameters. 143 columns (List[str]): 144 List of fields to be used as columns when pivoting the data. 145 format (str): 146 Format to be used to convert pandas.DataFrame to 147 dictionary, must be in ['dict','list','series', 148 'split', 'records','index']. 149 filter_dict (dict): 150 Same as list function. 151 exclude_dict (dict): 152 Same as list function. 153 order_by (List[str]): 154 Same as list function. 155 fields (List[str]): 156 List of the fields to be returned, if None, the default 157 variables will be returned. Same as fields on list functions. 158 variables (List[str]): 159 List of the fields to be returned, if None, the default 160 variables will be returned. Same as fields on list functions. 161 **DEPRECTED** use fields. 162 show_deleted (bool): 163 Fields with deleted column will have objects with deleted=True 164 omited from results. show_deleted=True will return this 165 information. 166 add_pk_column (bool): 167 If add pk values of the objects at pivot results. Adding 168 pk key on pivot end-points won't be possible to pivot since 169 pk is unique for each entry. 170 auth_header (dict): 171 Auth header to substitute the microservice original 172 at the request (user impersonation). 173 as_dataframe (bool): 174 If results should be returned as a dataframe. 175 176 Returns: 177 Return a list or a dictinary depending on the format set on 178 format parameter. 179 180 Raises: 181 PumpWoodException: 182 'Columns must be a list of elements.'. Indicates that the list 183 argument was not a list. 184 PumpWoodException: 185 'Column chosen as pivot is not at model variables'. Indicates 186 that columns that were set to pivot are not present on model 187 variables. 188 PumpWoodException: 189 "Format must be in ['dict','list','series','split', 190 'records','index']". Indicates that format set as paramenter 191 is not implemented. 192 PumpWoodException: 193 "Can not add pk column and pivot information". If 194 add_pk_column is True (results will have the pk column), it is 195 not possible to pivot the information (pk is an unique value 196 for each object, there is no reason to pivot it). 197 PumpWoodException: 198 "'value' column not at melted data, it is not possible 199 to pivot dataframe.". Indicates that data does not have a value 200 column, it must have it to populate pivoted table. 201 """ 202 # Deprect variables argument 203 if variables is not None: 204 msg = ( 205 "Deprecation Warning: pivot variable is deprected, " 206 "you should use fields instead") 207 logger.warning(msg) 208 fields = fields if fields is not None else variables 209 210 filter_dict = {} if filter_dict is None else filter_dict 211 exclude_dict = {} if exclude_dict is None else exclude_dict 212 order_by = [] if order_by is None else order_by 213 columns = [] if columns is None else columns 214 215 url_str = self._build_pivot_url(model_class) 216 post_data = { 217 'columns': columns, 'format': format, 218 'filter_dict': filter_dict, 'exclude_dict': exclude_dict, 219 'order_by': order_by, "variables": variables, 220 "show_deleted": show_deleted, "add_pk_column": add_pk_column} 221 pivot_results = self.request_post( 222 url=url_str, data=post_data, auth_header=auth_header) 223 224 if not add_pk_column: 225 if as_dataframe: 226 # If passed, set the fields as columns to ensure empty 227 # dataframes to have the correct fields 228 return pd.DataFrame(pivot_results, columns=fields) 229 else: 230 return pivot_results 231 else: 232 # If passed, set the fields as columns to ensure empty 233 # dataframes to have the correct fields 234 pd_pivot_results = pd.DataFrame(pivot_results, columns=fields) 235 if len(pd_pivot_results) != 0: 236 fill_options = self.fill_options( 237 model_class=model_class, auth_header=auth_header) 238 primary_keys_data = fill_options["pk"] 239 # Allow legacy pk definition and actual that uses extra_info 240 primary_keys = primary_keys_data\ 241 .get('extra_info', {})\ 242 .get('columns', primary_keys_data['column']) 243 pd_pivot_results["pk"] = pd_pivot_results[primary_keys].apply( 244 CompositePkBase64Converter.dump, 245 primary_keys=primary_keys, axis=1) 246 if as_dataframe: 247 return pd_pivot_results 248 else: 249 return pd_pivot_results.to_dict(format)
Pivot object data acording to columns specified.
Pivoting per-se is not usually used, beeing the name of the function a legacy. Normality data transformation is done at the client level.
Arguments:
- model_class (str): Model class to check search parameters.
- columns (List[str]): List of fields to be used as columns when pivoting the data.
- format (str): Format to be used to convert pandas.DataFrame to dictionary, must be in ['dict','list','series', 'split', 'records','index'].
- filter_dict (dict): Same as list function.
- exclude_dict (dict): Same as list function.
- order_by (List[str]): Same as list function.
- fields (List[str]): List of the fields to be returned, if None, the default variables will be returned. Same as fields on list functions.
- variables (List[str]): List of the fields to be returned, if None, the default variables will be returned. Same as fields on list functions. DEPRECTED use fields.
- show_deleted (bool): Fields with deleted column will have objects with deleted=True omited from results. show_deleted=True will return this information.
- add_pk_column (bool): If add pk values of the objects at pivot results. Adding pk key on pivot end-points won't be possible to pivot since pk is unique for each entry.
- auth_header (dict): Auth header to substitute the microservice original at the request (user impersonation).
- as_dataframe (bool): If results should be returned as a dataframe.
Returns:
Return a list or a dictinary depending on the format set on format parameter.
Raises:
- PumpWoodException: 'Columns must be a list of elements.'. Indicates that the list argument was not a list.
- PumpWoodException: 'Column chosen as pivot is not at model variables'. Indicates that columns that were set to pivot are not present on model variables.
- PumpWoodException: "Format must be in ['dict','list','series','split', 'records','index']". Indicates that format set as paramenter is not implemented.
- PumpWoodException: "Can not add pk column and pivot information". If add_pk_column is True (results will have the pk column), it is not possible to pivot the information (pk is an unique value for each object, there is no reason to pivot it).
- PumpWoodException: "'value' column not at melted data, it is not possible to pivot dataframe.". Indicates that data does not have a value column, it must have it to populate pivoted table.
299 def flat_list_by_chunks(self, model_class: str, filter_dict: dict = None, 300 exclude_dict: dict = None, 301 fields: List[str] = None, 302 show_deleted: bool = False, 303 auth_header: dict = None, 304 chunk_size: int = 1000000, 305 n_parallel: int = None, 306 create_composite_pk: bool = False, 307 start_date: str = None, 308 end_date: str = None, 309 time_column: str = 'time') -> pd.DataFrame: 310 """Incrementally fetch data from pivot end-point. 311 312 Fetch data from pivot end-point paginating by id of chunk_size length. 313 314 If table is partitioned it will split the query acording to partition 315 to facilitate query at the database. 316 317 If start_date and end_date are set, also breaks the query by month 318 retrieving each month data in parallel. 319 320 Args: 321 model_class (str): 322 Model class to be pivoted. 323 filter_dict (dict): 324 Dictionary to to be used in objects.filter argument 325 (Same as list end-point). 326 exclude_dict (dict): 327 Dictionary to to be used in objects.exclude argument 328 (Same as list end-point). 329 fields (List[str] | None): 330 List of the variables to be returned, 331 if None, the default variables will be returned. 332 If fields is set, dataframe will return that columns 333 even if data is empty. 334 start_date (datetime | str): 335 Set a begin date for the query. If begin and end date are 336 set, query will be splited with chucks by month that will be 337 requested in parallel. 338 end_date (datetime | str): 339 Set a end date for the query. If begin and end date are 340 set, query will be splited with chucks by month that will be 341 requested in parallel. 342 show_deleted (bool): 343 If deleted data should be returned. 344 auth_header (dict): 345 Auth header to substitute the microservice original 346 at the request (user impersonation). 347 chunk_size (int): 348 Limit of data to fetch per call. 349 n_parallel (int): 350 Number of parallel process to perform. 351 create_composite_pk (bool): 352 If true and table has a composite pk, it will create pk 353 value based on the hash on the json serialized dictionary 354 of the components of the primary key. 355 time_column (str): 356 Column that will be considered on the partitioning of data 357 fetch. 358 359 Returns: 360 Returns a dataframe with all information fetched. 361 362 Raises: 363 No particular raise. 364 """ 365 # Set empty dictionary for dictionary arguments 366 filter_dict = {} if filter_dict is None else filter_dict 367 exclude_dict = {} if exclude_dict is None else exclude_dict 368 original_fields = None if fields is None else fields 369 370 if n_parallel is None: 371 n_parallel = PUMPWOOD_COMUNICATION__N_PARALLEL 372 373 fill_options = self.fill_options( 374 model_class=model_class, auth_header=auth_header) 375 376 # Retrieve PK infomration about primary_key and partitions 377 primary_keys = fill_options["pk"]["extra_info"]['columns'] 378 # partition = fill_options["pk"]["extra_info"]['partition'] 379 380 # Add all primary keys fields to query if create_composite_pk is 381 # set true 382 if create_composite_pk and fields is not None: 383 fields = list(set(fields) | set(primary_keys)) 384 385 # Create a list of month and include start and end dates if not at 386 # the beginning of a month 387 month_sequence = None 388 if (start_date is not None) and (end_date is not None): 389 month_sequence = AuxFlatListByChunks.build_month_partitions( 390 start_date=start_date, 391 end_date=end_date) 392 elif (start_date is not None) or (end_date is not None): 393 # To create the partitions is necessary to have both start and end 394 # date. 395 msg = ( 396 "To break query in chunks using start_date and end_date " 397 "both must be set.\n- start_date: {start_date}\n" 398 "- end_date: {end_date}") 399 raise PumpWoodException( 400 message=msg, payload={ 401 "start_date": start_date, 402 "end_date": end_date}) 403 404 pool_arguments = AuxFlatListByChunks.build_query_partitions( 405 model_class=model_class, time_partitions=month_sequence, 406 filter_dict=filter_dict, exclude_dict=exclude_dict, fields=fields, 407 time_column=time_column, show_deleted=show_deleted, 408 auth_header=auth_header, chunk_size=chunk_size) 409 410 # Perform parallel calls to backend each chucked by chunk_size 411 print("## Starting parallel flat list: %s" % len(pool_arguments)) 412 resp_df = None 413 try: 414 with Pool(n_parallel) as p: 415 results = p.map( 416 self._flat_list_by_chunks_helper, 417 pool_arguments) 418 if len(results) != 0: 419 resp_df = pd.concat(results)\ 420 .reset_index(drop=True) 421 else: 422 resp_df = pd.DataFrame(columns=fields) 423 except Exception as e: 424 PumpWoodException(message=str(e)) 425 print("\n## Finished parallel flat list: %s" % len(pool_arguments)) 426 427 # Add the primary key as a column to the dataframe 428 resp_df = AuxFlatListByChunks.add_pk_column( 429 create_composite_pk=create_composite_pk, 430 primary_key_list=primary_keys, 431 data=resp_df) 432 433 # Limit the return fields 434 if original_fields is not None: 435 if create_composite_pk: 436 return pd.DataFrame( 437 resp_df, columns=['pk'] + original_fields) 438 else: 439 return pd.DataFrame( 440 resp_df, columns=original_fields) 441 else: 442 return resp_df
Incrementally fetch data from pivot end-point.
Fetch data from pivot end-point paginating by id of chunk_size length.
If table is partitioned it will split the query acording to partition to facilitate query at the database.
If start_date and end_date are set, also breaks the query by month retrieving each month data in parallel.
Arguments:
- model_class (str): Model class to be pivoted.
- filter_dict (dict): Dictionary to to be used in objects.filter argument (Same as list end-point).
- exclude_dict (dict): Dictionary to to be used in objects.exclude argument (Same as list end-point).
- fields (List[str] | None): List of the variables to be returned, if None, the default variables will be returned. If fields is set, dataframe will return that columns even if data is empty.
- start_date (datetime | str): Set a begin date for the query. If begin and end date are set, query will be splited with chucks by month that will be requested in parallel.
- end_date (datetime | str): Set a end date for the query. If begin and end date are set, query will be splited with chucks by month that will be requested in parallel.
- show_deleted (bool): If deleted data should be returned.
- auth_header (dict): Auth header to substitute the microservice original at the request (user impersonation).
- chunk_size (int): Limit of data to fetch per call.
- n_parallel (int): Number of parallel process to perform.
- create_composite_pk (bool): If true and table has a composite pk, it will create pk value based on the hash on the json serialized dictionary of the components of the primary key.
- time_column (str): Column that will be considered on the partitioning of data fetch.
Returns:
Returns a dataframe with all information fetched.
Raises:
- No particular raise.
444 def get_pks_from_unique_field(self, model_class: str, field: str, 445 values: List[Any]) -> pd.DataFrame: 446 """Get pk using unique fields values. 447 448 Use unique field values to retrieve pk of the objects. This end-point 449 is usefull for retrieving pks of the objects associated with unique 450 fields such as `description` (unique on most model of pumpwood). 451 452 ```python 453 # Using description to fetch pks from objects 454 data: pd.DataFrame = [data with unique description but without pk] 455 data['attribute_id'] = microservice.get_pks_from_unique_field( 456 model_class="DescriptionAttribute", 457 field="description", values=data['attribute'])['pk'] 458 459 # Using a dimension key to fetch pk of the objects, dimension 460 # key must be unique 461 data['georea_id'] = microservice.get_pks_from_unique_field( 462 model_class="DescriptionGeoarea", field="dimension->city", 463 values=data['city'])['pk'] 464 ``` 465 466 Args: 467 model_class: 468 Model class of the objects. 469 field: 470 Unique field to fetch pk. It is possible to use dimension keys 471 as unique field, for that use `dimension->[key]` notation. 472 values: 473 List of the unique fields used to fetch primary keys. 474 475 Return: 476 Return a dataframe in same order as values with columns: 477 - **pk**: Correspondent primary key of the unique value. 478 - **[field]**: Column with same name of field argument, 479 correspondent to pk. 480 481 Raises: 482 PumpWoodQueryException: 483 Raises if field is not found on the model and it is note 484 associated with a dimension tag. 485 PumpWoodQueryException: 486 Raises if `field` does not have a unique restriction on 487 database. Dimension keys does not check for uniqueness on 488 database, be carefull not to duplicate the lines. 489 """ 490 is_dimension_tag = 'dimensions->' in field 491 if not is_dimension_tag: 492 fill_options = self.fill_options(model_class=model_class) 493 field_details = fill_options.get(field) 494 if field_details is None: 495 msg = ( 496 "Field is not a dimension tag and not found on model " 497 "fields. Field [{field}]") 498 raise PumpWoodQueryException( 499 message=msg, payload={"field": field}) 500 501 is_unique_field = field_details.get("unique", False) 502 if not is_unique_field: 503 msg = "Field [{field}] to get pk from is not unique" 504 raise PumpWoodQueryException( 505 message=msg, payload={"field": field}) 506 507 filter_dict = {field + "__in": list(set(values))} 508 pk_map = None 509 if not is_dimension_tag: 510 list_results = pd.DataFrame(self.list_without_pag( 511 model_class=model_class, filter_dict=filter_dict, 512 fields=["pk", field]), columns=["pk", field]) 513 pk_map = list_results.set_index(field)["pk"] 514 515 # If is dimension tag, fetch dimension and unpack it 516 else: 517 dimension_tag = field.split("->")[1] 518 list_results = pd.DataFrame(self.list_without_pag( 519 model_class=model_class, filter_dict=filter_dict, 520 fields=["pk", "dimensions"])) 521 pk_map = {} 522 if len(list_results) != 0: 523 pk_map = list_results\ 524 .pipe(unpack_dict_columns, columns=["dimensions"])\ 525 .set_index(dimension_tag)["pk"] 526 527 values_series = pd.Series(values) 528 return pd.DataFrame({ 529 "pk": values_series.map(pk_map).to_numpy(), 530 field: values_series 531 })
Get pk using unique fields values.
Use unique field values to retrieve pk of the objects. This end-point
is usefull for retrieving pks of the objects associated with unique
fields such as description (unique on most model of pumpwood).
# Using description to fetch pks from objects
data: pd.DataFrame = [data with unique description but without pk]
data['attribute_id'] = microservice.get_pks_from_unique_field(
model_class="DescriptionAttribute",
field="description", values=data['attribute'])['pk']
# Using a dimension key to fetch pk of the objects, dimension
# key must be unique
data['georea_id'] = microservice.get_pks_from_unique_field(
model_class="DescriptionGeoarea", field="dimension->city",
values=data['city'])['pk']
Arguments:
- model_class: Model class of the objects.
- field: Unique field to fetch pk. It is possible to use dimension keys
as unique field, for that use
dimension->[key]notation. - values: List of the unique fields used to fetch primary keys.
Return:
Return a dataframe in same order as values with columns:
- pk: Correspondent primary key of the unique value.
- [field]: Column with same name of field argument, correspondent to pk.
Raises:
- PumpWoodQueryException: Raises if field is not found on the model and it is note associated with a dimension tag.
- PumpWoodQueryException: Raises if
fielddoes not have a unique restriction on database. Dimension keys does not check for uniqueness on database, be carefull not to duplicate the lines.
16class ABCSimpleRetriveMicroservice(ABC, PumpWoodMicroServiceBase): 17 """Abstract class for parallel calls at Pumpwood end-points.""" 18 19 @staticmethod 20 def _build_list_one_url(model_class, pk): 21 return "rest/%s/retrieve/%s/" % (model_class.lower(), pk) 22 23 def list_one(self, model_class: str, pk: int, fields: list = None, 24 default_fields: bool = True, foreign_key_fields: bool = False, 25 related_fields: bool = False, auth_header: dict = None, 26 use_disk_cache: bool = False, use_app_cache: bool = False, 27 disk_cache_expire: int = None, 28 base_filter_skip: list = None) -> dict: 29 """Retrieve an object using list serializer (simple). 30 31 **# DEPRECTED #** It is the same as retrieve using 32 `default_fields: bool = True`, if possible migrate to retrieve 33 function. 34 35 Args: 36 model_class: 37 Model class of the end-point 38 pk: 39 Object pk 40 auth_header: 41 Auth header to substitute the microservice original 42 at the request (user impersonation). 43 fields: 44 Set the fields to be returned by the list end-point. 45 default_fields: 46 Boolean, if true and fields arguments None will return the 47 default fields set for list by the backend. 48 foreign_key_fields: 49 Return forenging key objects. It will return the fk 50 corresponding object. Ex: `created_by_id` reference to 51 a user `model_class` the correspondent to User will be 52 returned at `created_by`. 53 related_fields: 54 Return related fields objects. Related field objects are 55 objects that have a forenging key associated with this 56 model_class, results will be returned as a list of 57 dictionaries usually in a field with `_set` at end. 58 Returning related_fields consume backend resorces, use 59 carefully. 60 use_disk_cache (bool): 61 If set true, get request will use local cache to reduce 62 the requests to the backend. 63 use_app_cache (bool): 64 If True, the GET request will use the cache of the application. 65 Defaults to False. 66 disk_cache_expire (int): 67 Time in seconds to expire the cache, it None it will 68 use de default set be PumpwoodCache. 69 base_filter_skip (list[str]): 70 List of base query filter to be skiped, it is necessary to 71 be superuser to skip base query filters. 72 73 Returns: 74 Return object with the correspondent pk. 75 76 Raises: 77 PumpWoodObjectDoesNotExist: 78 If pk not found on database. 79 """ 80 base_filter_skip = self._resolve_base_filter_skip( 81 base_filter_skip) 82 url_str = self._build_list_one_url(model_class, pk) 83 return self.request_get( 84 url=url_str, parameters={ 85 "fields": fields, "default_fields": default_fields, 86 "foreign_key_fields": foreign_key_fields, 87 "related_fields": related_fields, 88 "base_filter_skip": base_filter_skip, 89 "use_cache": use_app_cache}, 90 auth_header=auth_header, use_disk_cache=use_disk_cache, 91 disk_cache_expire=disk_cache_expire) 92 93 @staticmethod 94 def _build_retrieve_url(model_class: str, pk: int): 95 return "rest/%s/retrieve/%s/" % (model_class.lower(), pk) 96 97 def retrieve(self, model_class: str, pk: int | str | dict, 98 default_fields: bool = False, 99 foreign_key_fields: bool = False, 100 related_fields: bool = False, 101 fields: list = None, 102 auth_header: dict = None, 103 use_disk_cache: bool = False, 104 use_app_cache: bool = False, 105 disk_cache_expire: int = None, 106 base_filter_skip: list = None) -> dict: 107 """Retrieve an object from PumpWood. 108 109 Function to get an object serialized by the retrieve endpoint (more 110 detailed data). It will fetch information for a single object 111 based on the primary key, which may be a simple ID, a composite key 112 passed as a dictionary, or a base64 URL-safe string. 113 114 It is also possible to retrieve single objects using unique fields, 115 such as codes or multiple column uniqueness constraints. This can 116 be done by passing the argument as a base64 string or a dictionary 117 containing the filtering clauses. 118 119 Example: 120 ```python 121 microservice.retrieve( 122 model_class="ModelClassWithUniqueCode", 123 pk={'code': 'unique code for object'}) 124 125 microservice.retrieve( 126 model_class="ModelClassWithCompositeUniqueConstraint", 127 pk={'time': '2026-01-01', 'attribute_id': 1}) 128 ``` 129 130 Args: 131 model_class (str): 132 Model class of the endpoint. 133 pk (Union[int, str, dict]): 134 The primary key or unique identifier for the object. 135 auth_header (dict, optional): 136 Authentication header to substitute the microservice's original 137 credentials (used for user impersonation). Defaults to None. 138 fields (list, optional): 139 Set of fields to be returned by the endpoint. 140 default_fields (bool): 141 If True and 'fields' is None, will return the default fields 142 defined by the backend. Defaults to False. 143 foreign_key_fields (bool): 144 If True, returns full objects for foreign keys instead of just 145 their IDs. For example, 'created_by_id' will also return the 146 user object at 'created_by'. Defaults to False. 147 related_fields (bool): 148 If True, returns related objects (those that have a foreign key 149 pointing to this model). Results are typically returned as a 150 list of dictionaries in a field with a '_set' suffix. 151 Warning: Using this may consume significant backend resources. 152 Defaults to False. 153 use_disk_cache (bool): 154 If True, the GET request will use a local disk cache to reduce 155 backend load. Defaults to False. 156 use_app_cache (bool): 157 If True, the GET request will use the cache of the application. 158 Defaults to False. 159 disk_cache_expire (int, optional): 160 TTL in seconds for the cache. If None, uses the default 161 PumpwoodCache settings. Defaults to None. 162 base_filter_skip (list, optional): 163 List of base query filters to skip. Requires superuser 164 privileges. Defaults to None. 165 166 Returns: 167 dict: The object matching the provided primary key/identifier. 168 169 Raises: 170 PumpWoodObjectDoesNotExist: 171 If the PK is not found in the database. 172 PumpWoodException: 173 For other errors during retrieval or communication. 174 """ 175 # Type checking and complex default values 176 is_allowed_types = isinstance( 177 pk, (numbers.Number, np.number, Decimal, str, dict)) 178 if not is_allowed_types: 179 msg = ( 180 "Retrieve pk must be a number, string or dict," 181 " got type [{type}]") 182 raise PumpWoodException( 183 msg, payload={"type": type(pk).__name__}) 184 185 base_filter_skip = self._resolve_base_filter_skip( 186 base_filter_skip) 187 188 # Convert to base64 dict unique queries 189 serialized_pk = None 190 if isinstance(pk, dict): 191 # Use the correct keyword argument 'primary_key_dict' 192 serialized_pk = CompositePkBase64Converter.dump_dict( 193 primary_key_dict=pk) 194 else: 195 serialized_pk = pk 196 197 # Fetch information from Pumpwood 198 url_str = self._build_retrieve_url( 199 model_class=model_class, pk=serialized_pk) 200 return self.request_get( 201 url=url_str, parameters={ 202 "fields": fields, "default_fields": default_fields, 203 "foreign_key_fields": foreign_key_fields, 204 "related_fields": related_fields, 205 "base_filter_skip": base_filter_skip, 206 "use_cache": use_app_cache}, 207 auth_header=auth_header, use_disk_cache=use_disk_cache, 208 disk_cache_expire=disk_cache_expire) 209 210 @staticmethod 211 def _build_retrieve_file_url(model_class: str, pk: int): 212 return "rest/%s/retrieve-file/%s/" % (model_class.lower(), pk) 213 214 def retrieve_file(self, model_class: str, pk: int, file_field: str, 215 auth_header: dict = None, save_file: bool = True, 216 save_path: str = "./", file_name: str = None, 217 if_exists: str = "fail", 218 base_filter_skip: list = None) -> any: 219 """Retrieve a file from PumpWood. 220 221 This function will retrieve file as a single request, depending on the 222 size of the files it would be preferred to use streaming end-point. 223 224 Args: 225 model_class: 226 Class of the model to retrieve file. 227 pk: 228 Pk of the object associeted file. 229 file_field: 230 Field of the file to be downloaded. 231 auth_header: 232 Dictionary containing the auth header. 233 save_file: 234 If data is to be saved as file or return get 235 response. 236 save_path: 237 Path of the directory to save file. 238 file_name: 239 Name of the file, if None it will have same name as 240 saved in PumpWood. 241 if_exists: 242 Values must be in {'fail', 'change_name', 'overwrite', 'skip'}. 243 Set what to do if there is a file with same name. Skip 244 will not download file if there is already with same 245 os.path.join(save_path, file_name), file_name must be set 246 for skip argument. 247 auth_header: 248 Auth header to substitute the microservice original 249 at the request (user impersonation). 250 base_filter_skip (list): 251 List of base query filter to be skiped, it is necessary to 252 be superuser to skip base query filters. 253 254 Returns: 255 May return the file name if save_file=True; If false will return 256 a dictonary with keys `filename` with original file name and 257 `content` with binary data of file content. 258 259 Raises: 260 PumpWoodForbidden: 261 'storage_object attribute not set for view, file operations 262 are disable'. This indicates that storage for this backend 263 was not configured, so it is not possible to make storage 264 operations, 265 PumpWoodForbidden: 266 'file_field must be set on self.file_fields dictionary'. This 267 indicates that the `file_field` parameter is not listed as 268 a file field on the backend. 269 PumpWoodObjectDoesNotExist: 270 'field [{}] not found or null at object'. This indicates that 271 the file field requested is not present on object fields. 272 PumpWoodObjectDoesNotExist: 273 'Object not found in storage [{}]'. This indicates that the 274 file associated with file_field is not avaiable at the 275 storage. This should not ocorrur, it might have a manual 276 update at the model_class table or manual removal/rename of 277 files on storage. 278 """ 279 base_filter_skip = self._resolve_base_filter_skip( 280 base_filter_skip) 281 282 if if_exists not in ["fail", "change_name", "overwrite", "skip"]: 283 raise PumpWoodException( 284 "if_exists must be in ['fail', 'change_name', 'overwrite', " 285 "'skip']") 286 287 if file_name is not None and if_exists == 'skip': 288 file_path = os.path.join(save_path, file_name) 289 is_file_already = os.path.isfile(file_path) 290 if is_file_already: 291 print("skiping file already exists: ", file_path) 292 return file_path 293 294 url_str = self._build_retrieve_file_url(model_class=model_class, pk=pk) 295 file_response = self.request_get( 296 url=url_str, parameters={ 297 "file-field": file_field, 298 "base_filter_skip": base_filter_skip}, 299 auth_header=auth_header) 300 if not save_file: 301 return file_response 302 303 if not os.path.exists(save_path): 304 raise PumpWoodException( 305 "Path to save retrieved file [{}] does not exist".format( 306 save_path)) 307 308 file_name = secure_filename(file_name or file_response["filename"]) 309 file_path = os.path.join(save_path, file_name) 310 is_file_already = os.path.isfile(file_path) 311 if is_file_already: 312 if if_exists == "change_name": 313 filename, file_extension = os.path.splitext(file_path) 314 too_many_tries = True 315 for i in range(10): 316 new_path = "{filename}__{count}{extension}".format( 317 filename=filename, count=i, 318 extension=file_extension) 319 if not os.path.isfile(new_path): 320 file_path = new_path 321 too_many_tries = False 322 break 323 if too_many_tries: 324 raise PumpWoodException( 325 ("Too many tries to find a not used file name." + 326 " file_path[{}]".format(file_path))) 327 328 elif if_exists == "fail": 329 raise PumpWoodException( 330 ("if_exists set as 'fail' and there is a file with same" + 331 "name. file_path [{}]").format(file_path)) 332 333 with open(file_path, "wb") as file: 334 file.write(file_response["content"]) 335 return file_path 336 337 @staticmethod 338 def _build_retrieve_file_straming_url(model_class: str, pk: int): 339 return "rest/%s/retrieve-file-streaming/%s/" % ( 340 model_class.lower(), pk) 341 342 def retrieve_streaming_file(self, model_class: str, pk: int, 343 file_field: str, file_name: str, 344 auth_header: dict = None, 345 save_path: str = "./", 346 if_exists: str = "fail", 347 base_filter_skip: list = None) -> str: 348 """Retrieve a file from PumpWood using streaming to retrieve content. 349 350 This funcion uses file streaming to retrieve file content, it should be 351 prefered when dealing with large (bigger than 10Mb) files transfer. 352 Using this end-point the file is not loaded on backend memory content 353 is transfered by chucks that are read at the storage and transfered 354 to user. 355 356 It will necessarily save the content as a file, there is not the 357 possibility of retrieving the content directly from request. 358 359 Args: 360 model_class: 361 Class of the model to retrieve file. 362 pk: 363 Pk of the object associeted file. 364 file_field: 365 Field of the file to be downloaded. 366 auth_header: 367 Dictionary containing the auth header. 368 save_path: 369 Path of the directory to save file. 370 file_name: 371 Name of the file, if None it will have same name as 372 saved in PumpWood. 373 if_exists: 374 Values must be in {'fail', 'change_name', 'overwrite'}. 375 Set what to do if there is a file with same name. 376 auth_header: 377 Auth header to substitute the microservice original 378 at the request (user impersonation). 379 base_filter_skip (list): 380 List of base query filter to be skiped, it is necessary to 381 be superuser to skip base query filters. 382 383 Returns: 384 Returns the file path that recived the file content. 385 386 Raises: 387 PumpWoodForbidden: 388 'storage_object attribute not set for view, file operations 389 are disable'. This indicates that storage for this backend 390 was not configured, so it is not possible to make storage 391 operations, 392 PumpWoodForbidden: 393 'file_field must be set on self.file_fields dictionary'. This 394 indicates that the `file_field` parameter is not listed as 395 a file field on the backend. 396 PumpWoodObjectDoesNotExist: 397 'field [{}] not found or null at object'. This indicates that 398 the file field requested is not present on object fields. 399 PumpWoodObjectDoesNotExist: 400 'Object not found in storage [{}]'. This indicates that the 401 file associated with file_field is not avaiable at the 402 storage. This should not ocorrur, it might have a manual 403 update at the model_class table or manual removal/rename of 404 files on storage. 405 """ 406 base_filter_skip = self._resolve_base_filter_skip( 407 base_filter_skip) 408 request_header = self._check_auth_header(auth_header) 409 410 if if_exists not in ["fail", "change_name", "overwrite"]: 411 raise PumpWoodException( 412 "if_exists must be in ['fail', 'change_name', 'overwrite']") 413 if not os.path.exists(save_path): 414 raise PumpWoodException( 415 "Path to save retrieved file [{}] does not exist".format( 416 save_path)) 417 418 file_path = os.path.join(save_path, file_name) 419 if os.path.isfile(file_path) and if_exists == "change_name": 420 filename, file_extension = os.path.splitext(file_path) 421 too_many_tries = False 422 for i in range(10): 423 new_path = "{filename}__{count}{extension}".format( 424 filename=filename, count=i, 425 extension=file_extension) 426 if not os.path.isfile(new_path): 427 file_path = new_path 428 too_many_tries = True 429 break 430 if not too_many_tries: 431 raise PumpWoodException( 432 ("Too many tries to find a not used file name." + 433 " file_path[{}]".format(file_path))) 434 435 if os.path.isfile(file_path) and if_exists == "fail": 436 raise PumpWoodException( 437 ("if_exists set as 'fail' and there is a file with same" + 438 "name. file_path [{}]").format(file_path)) 439 440 url_str = self._build_retrieve_file_straming_url( 441 model_class=model_class, pk=pk) 442 443 get_url = self.server_url + url_str 444 get_params = { 445 "file-field": file_field, 446 "base_filter_skip": base_filter_skip} 447 dumped_parameters = self._dump_query_parameters(parameters=get_params) 448 with requests.get( 449 get_url, verify=self._verify_ssl, headers=request_header, 450 params=dumped_parameters, 451 timeout=self._default_timeout) as response: 452 self.error_handler(response) 453 with open(file_path, 'wb') as f: 454 for chunk in response.iter_content(chunk_size=8192): 455 if chunk: 456 f.write(chunk) 457 return file_path
Abstract class for parallel calls at Pumpwood end-points.
23 def list_one(self, model_class: str, pk: int, fields: list = None, 24 default_fields: bool = True, foreign_key_fields: bool = False, 25 related_fields: bool = False, auth_header: dict = None, 26 use_disk_cache: bool = False, use_app_cache: bool = False, 27 disk_cache_expire: int = None, 28 base_filter_skip: list = None) -> dict: 29 """Retrieve an object using list serializer (simple). 30 31 **# DEPRECTED #** It is the same as retrieve using 32 `default_fields: bool = True`, if possible migrate to retrieve 33 function. 34 35 Args: 36 model_class: 37 Model class of the end-point 38 pk: 39 Object pk 40 auth_header: 41 Auth header to substitute the microservice original 42 at the request (user impersonation). 43 fields: 44 Set the fields to be returned by the list end-point. 45 default_fields: 46 Boolean, if true and fields arguments None will return the 47 default fields set for list by the backend. 48 foreign_key_fields: 49 Return forenging key objects. It will return the fk 50 corresponding object. Ex: `created_by_id` reference to 51 a user `model_class` the correspondent to User will be 52 returned at `created_by`. 53 related_fields: 54 Return related fields objects. Related field objects are 55 objects that have a forenging key associated with this 56 model_class, results will be returned as a list of 57 dictionaries usually in a field with `_set` at end. 58 Returning related_fields consume backend resorces, use 59 carefully. 60 use_disk_cache (bool): 61 If set true, get request will use local cache to reduce 62 the requests to the backend. 63 use_app_cache (bool): 64 If True, the GET request will use the cache of the application. 65 Defaults to False. 66 disk_cache_expire (int): 67 Time in seconds to expire the cache, it None it will 68 use de default set be PumpwoodCache. 69 base_filter_skip (list[str]): 70 List of base query filter to be skiped, it is necessary to 71 be superuser to skip base query filters. 72 73 Returns: 74 Return object with the correspondent pk. 75 76 Raises: 77 PumpWoodObjectDoesNotExist: 78 If pk not found on database. 79 """ 80 base_filter_skip = self._resolve_base_filter_skip( 81 base_filter_skip) 82 url_str = self._build_list_one_url(model_class, pk) 83 return self.request_get( 84 url=url_str, parameters={ 85 "fields": fields, "default_fields": default_fields, 86 "foreign_key_fields": foreign_key_fields, 87 "related_fields": related_fields, 88 "base_filter_skip": base_filter_skip, 89 "use_cache": use_app_cache}, 90 auth_header=auth_header, use_disk_cache=use_disk_cache, 91 disk_cache_expire=disk_cache_expire)
Retrieve an object using list serializer (simple).
# DEPRECTED # It is the same as retrieve using
default_fields: bool = True, if possible migrate to retrieve
function.
Arguments:
- model_class: Model class of the end-point
- pk: Object pk
- auth_header: Auth header to substitute the microservice original at the request (user impersonation).
- fields: Set the fields to be returned by the list end-point.
- default_fields: Boolean, if true and fields arguments None will return the default fields set for list by the backend.
- foreign_key_fields: Return forenging key objects. It will return the fk
corresponding object. Ex:
created_by_idreference to a usermodel_classthe correspondent to User will be returned atcreated_by. - related_fields: Return related fields objects. Related field objects are
objects that have a forenging key associated with this
model_class, results will be returned as a list of
dictionaries usually in a field with
_setat end. Returning related_fields consume backend resorces, use carefully. - use_disk_cache (bool): If set true, get request will use local cache to reduce the requests to the backend.
- use_app_cache (bool): If True, the GET request will use the cache of the application. Defaults to False.
- disk_cache_expire (int): Time in seconds to expire the cache, it None it will use de default set be PumpwoodCache.
- base_filter_skip (list[str]): List of base query filter to be skiped, it is necessary to be superuser to skip base query filters.
Returns:
Return object with the correspondent pk.
Raises:
- PumpWoodObjectDoesNotExist: If pk not found on database.
97 def retrieve(self, model_class: str, pk: int | str | dict, 98 default_fields: bool = False, 99 foreign_key_fields: bool = False, 100 related_fields: bool = False, 101 fields: list = None, 102 auth_header: dict = None, 103 use_disk_cache: bool = False, 104 use_app_cache: bool = False, 105 disk_cache_expire: int = None, 106 base_filter_skip: list = None) -> dict: 107 """Retrieve an object from PumpWood. 108 109 Function to get an object serialized by the retrieve endpoint (more 110 detailed data). It will fetch information for a single object 111 based on the primary key, which may be a simple ID, a composite key 112 passed as a dictionary, or a base64 URL-safe string. 113 114 It is also possible to retrieve single objects using unique fields, 115 such as codes or multiple column uniqueness constraints. This can 116 be done by passing the argument as a base64 string or a dictionary 117 containing the filtering clauses. 118 119 Example: 120 ```python 121 microservice.retrieve( 122 model_class="ModelClassWithUniqueCode", 123 pk={'code': 'unique code for object'}) 124 125 microservice.retrieve( 126 model_class="ModelClassWithCompositeUniqueConstraint", 127 pk={'time': '2026-01-01', 'attribute_id': 1}) 128 ``` 129 130 Args: 131 model_class (str): 132 Model class of the endpoint. 133 pk (Union[int, str, dict]): 134 The primary key or unique identifier for the object. 135 auth_header (dict, optional): 136 Authentication header to substitute the microservice's original 137 credentials (used for user impersonation). Defaults to None. 138 fields (list, optional): 139 Set of fields to be returned by the endpoint. 140 default_fields (bool): 141 If True and 'fields' is None, will return the default fields 142 defined by the backend. Defaults to False. 143 foreign_key_fields (bool): 144 If True, returns full objects for foreign keys instead of just 145 their IDs. For example, 'created_by_id' will also return the 146 user object at 'created_by'. Defaults to False. 147 related_fields (bool): 148 If True, returns related objects (those that have a foreign key 149 pointing to this model). Results are typically returned as a 150 list of dictionaries in a field with a '_set' suffix. 151 Warning: Using this may consume significant backend resources. 152 Defaults to False. 153 use_disk_cache (bool): 154 If True, the GET request will use a local disk cache to reduce 155 backend load. Defaults to False. 156 use_app_cache (bool): 157 If True, the GET request will use the cache of the application. 158 Defaults to False. 159 disk_cache_expire (int, optional): 160 TTL in seconds for the cache. If None, uses the default 161 PumpwoodCache settings. Defaults to None. 162 base_filter_skip (list, optional): 163 List of base query filters to skip. Requires superuser 164 privileges. Defaults to None. 165 166 Returns: 167 dict: The object matching the provided primary key/identifier. 168 169 Raises: 170 PumpWoodObjectDoesNotExist: 171 If the PK is not found in the database. 172 PumpWoodException: 173 For other errors during retrieval or communication. 174 """ 175 # Type checking and complex default values 176 is_allowed_types = isinstance( 177 pk, (numbers.Number, np.number, Decimal, str, dict)) 178 if not is_allowed_types: 179 msg = ( 180 "Retrieve pk must be a number, string or dict," 181 " got type [{type}]") 182 raise PumpWoodException( 183 msg, payload={"type": type(pk).__name__}) 184 185 base_filter_skip = self._resolve_base_filter_skip( 186 base_filter_skip) 187 188 # Convert to base64 dict unique queries 189 serialized_pk = None 190 if isinstance(pk, dict): 191 # Use the correct keyword argument 'primary_key_dict' 192 serialized_pk = CompositePkBase64Converter.dump_dict( 193 primary_key_dict=pk) 194 else: 195 serialized_pk = pk 196 197 # Fetch information from Pumpwood 198 url_str = self._build_retrieve_url( 199 model_class=model_class, pk=serialized_pk) 200 return self.request_get( 201 url=url_str, parameters={ 202 "fields": fields, "default_fields": default_fields, 203 "foreign_key_fields": foreign_key_fields, 204 "related_fields": related_fields, 205 "base_filter_skip": base_filter_skip, 206 "use_cache": use_app_cache}, 207 auth_header=auth_header, use_disk_cache=use_disk_cache, 208 disk_cache_expire=disk_cache_expire)
Retrieve an object from PumpWood.
Function to get an object serialized by the retrieve endpoint (more detailed data). It will fetch information for a single object based on the primary key, which may be a simple ID, a composite key passed as a dictionary, or a base64 URL-safe string.
It is also possible to retrieve single objects using unique fields, such as codes or multiple column uniqueness constraints. This can be done by passing the argument as a base64 string or a dictionary containing the filtering clauses.
Example:
microservice.retrieve( model_class="ModelClassWithUniqueCode", pk={'code': 'unique code for object'}) microservice.retrieve( model_class="ModelClassWithCompositeUniqueConstraint", pk={'time': '2026-01-01', 'attribute_id': 1})
Arguments:
- model_class (str): Model class of the endpoint.
- pk (Union[int, str, dict]): The primary key or unique identifier for the object.
- auth_header (dict, optional): Authentication header to substitute the microservice's original credentials (used for user impersonation). Defaults to None.
- fields (list, optional): Set of fields to be returned by the endpoint.
- default_fields (bool): If True and 'fields' is None, will return the default fields defined by the backend. Defaults to False.
- foreign_key_fields (bool): If True, returns full objects for foreign keys instead of just their IDs. For example, 'created_by_id' will also return the user object at 'created_by'. Defaults to False.
- related_fields (bool): If True, returns related objects (those that have a foreign key pointing to this model). Results are typically returned as a list of dictionaries in a field with a '_set' suffix. Warning: Using this may consume significant backend resources. Defaults to False.
- use_disk_cache (bool): If True, the GET request will use a local disk cache to reduce backend load. Defaults to False.
- use_app_cache (bool): If True, the GET request will use the cache of the application. Defaults to False.
- disk_cache_expire (int, optional): TTL in seconds for the cache. If None, uses the default PumpwoodCache settings. Defaults to None.
- base_filter_skip (list, optional): List of base query filters to skip. Requires superuser privileges. Defaults to None.
Returns:
dict: The object matching the provided primary key/identifier.
Raises:
- PumpWoodObjectDoesNotExist: If the PK is not found in the database.
- PumpWoodException: For other errors during retrieval or communication.
214 def retrieve_file(self, model_class: str, pk: int, file_field: str, 215 auth_header: dict = None, save_file: bool = True, 216 save_path: str = "./", file_name: str = None, 217 if_exists: str = "fail", 218 base_filter_skip: list = None) -> any: 219 """Retrieve a file from PumpWood. 220 221 This function will retrieve file as a single request, depending on the 222 size of the files it would be preferred to use streaming end-point. 223 224 Args: 225 model_class: 226 Class of the model to retrieve file. 227 pk: 228 Pk of the object associeted file. 229 file_field: 230 Field of the file to be downloaded. 231 auth_header: 232 Dictionary containing the auth header. 233 save_file: 234 If data is to be saved as file or return get 235 response. 236 save_path: 237 Path of the directory to save file. 238 file_name: 239 Name of the file, if None it will have same name as 240 saved in PumpWood. 241 if_exists: 242 Values must be in {'fail', 'change_name', 'overwrite', 'skip'}. 243 Set what to do if there is a file with same name. Skip 244 will not download file if there is already with same 245 os.path.join(save_path, file_name), file_name must be set 246 for skip argument. 247 auth_header: 248 Auth header to substitute the microservice original 249 at the request (user impersonation). 250 base_filter_skip (list): 251 List of base query filter to be skiped, it is necessary to 252 be superuser to skip base query filters. 253 254 Returns: 255 May return the file name if save_file=True; If false will return 256 a dictonary with keys `filename` with original file name and 257 `content` with binary data of file content. 258 259 Raises: 260 PumpWoodForbidden: 261 'storage_object attribute not set for view, file operations 262 are disable'. This indicates that storage for this backend 263 was not configured, so it is not possible to make storage 264 operations, 265 PumpWoodForbidden: 266 'file_field must be set on self.file_fields dictionary'. This 267 indicates that the `file_field` parameter is not listed as 268 a file field on the backend. 269 PumpWoodObjectDoesNotExist: 270 'field [{}] not found or null at object'. This indicates that 271 the file field requested is not present on object fields. 272 PumpWoodObjectDoesNotExist: 273 'Object not found in storage [{}]'. This indicates that the 274 file associated with file_field is not avaiable at the 275 storage. This should not ocorrur, it might have a manual 276 update at the model_class table or manual removal/rename of 277 files on storage. 278 """ 279 base_filter_skip = self._resolve_base_filter_skip( 280 base_filter_skip) 281 282 if if_exists not in ["fail", "change_name", "overwrite", "skip"]: 283 raise PumpWoodException( 284 "if_exists must be in ['fail', 'change_name', 'overwrite', " 285 "'skip']") 286 287 if file_name is not None and if_exists == 'skip': 288 file_path = os.path.join(save_path, file_name) 289 is_file_already = os.path.isfile(file_path) 290 if is_file_already: 291 print("skiping file already exists: ", file_path) 292 return file_path 293 294 url_str = self._build_retrieve_file_url(model_class=model_class, pk=pk) 295 file_response = self.request_get( 296 url=url_str, parameters={ 297 "file-field": file_field, 298 "base_filter_skip": base_filter_skip}, 299 auth_header=auth_header) 300 if not save_file: 301 return file_response 302 303 if not os.path.exists(save_path): 304 raise PumpWoodException( 305 "Path to save retrieved file [{}] does not exist".format( 306 save_path)) 307 308 file_name = secure_filename(file_name or file_response["filename"]) 309 file_path = os.path.join(save_path, file_name) 310 is_file_already = os.path.isfile(file_path) 311 if is_file_already: 312 if if_exists == "change_name": 313 filename, file_extension = os.path.splitext(file_path) 314 too_many_tries = True 315 for i in range(10): 316 new_path = "{filename}__{count}{extension}".format( 317 filename=filename, count=i, 318 extension=file_extension) 319 if not os.path.isfile(new_path): 320 file_path = new_path 321 too_many_tries = False 322 break 323 if too_many_tries: 324 raise PumpWoodException( 325 ("Too many tries to find a not used file name." + 326 " file_path[{}]".format(file_path))) 327 328 elif if_exists == "fail": 329 raise PumpWoodException( 330 ("if_exists set as 'fail' and there is a file with same" + 331 "name. file_path [{}]").format(file_path)) 332 333 with open(file_path, "wb") as file: 334 file.write(file_response["content"]) 335 return file_path
Retrieve a file from PumpWood.
This function will retrieve file as a single request, depending on the size of the files it would be preferred to use streaming end-point.
Arguments:
- model_class: Class of the model to retrieve file.
- pk: Pk of the object associeted file.
- file_field: Field of the file to be downloaded.
- auth_header: Dictionary containing the auth header.
- save_file: If data is to be saved as file or return get response.
- save_path: Path of the directory to save file.
- file_name: Name of the file, if None it will have same name as saved in PumpWood.
- if_exists: Values must be in {'fail', 'change_name', 'overwrite', 'skip'}. Set what to do if there is a file with same name. Skip will not download file if there is already with same os.path.join(save_path, file_name), file_name must be set for skip argument.
- auth_header: Auth header to substitute the microservice original at the request (user impersonation).
- base_filter_skip (list): List of base query filter to be skiped, it is necessary to be superuser to skip base query filters.
Returns:
May return the file name if save_file=True; If false will return a dictonary with keys
filenamewith original file name andcontentwith binary data of file content.
Raises:
- PumpWoodForbidden: 'storage_object attribute not set for view, file operations are disable'. This indicates that storage for this backend was not configured, so it is not possible to make storage operations,
- PumpWoodForbidden: 'file_field must be set on self.file_fields dictionary'. This
indicates that the
file_fieldparameter is not listed as a file field on the backend. - PumpWoodObjectDoesNotExist: 'field [{}] not found or null at object'. This indicates that the file field requested is not present on object fields.
- PumpWoodObjectDoesNotExist: 'Object not found in storage [{}]'. This indicates that the file associated with file_field is not avaiable at the storage. This should not ocorrur, it might have a manual update at the model_class table or manual removal/rename of files on storage.
342 def retrieve_streaming_file(self, model_class: str, pk: int, 343 file_field: str, file_name: str, 344 auth_header: dict = None, 345 save_path: str = "./", 346 if_exists: str = "fail", 347 base_filter_skip: list = None) -> str: 348 """Retrieve a file from PumpWood using streaming to retrieve content. 349 350 This funcion uses file streaming to retrieve file content, it should be 351 prefered when dealing with large (bigger than 10Mb) files transfer. 352 Using this end-point the file is not loaded on backend memory content 353 is transfered by chucks that are read at the storage and transfered 354 to user. 355 356 It will necessarily save the content as a file, there is not the 357 possibility of retrieving the content directly from request. 358 359 Args: 360 model_class: 361 Class of the model to retrieve file. 362 pk: 363 Pk of the object associeted file. 364 file_field: 365 Field of the file to be downloaded. 366 auth_header: 367 Dictionary containing the auth header. 368 save_path: 369 Path of the directory to save file. 370 file_name: 371 Name of the file, if None it will have same name as 372 saved in PumpWood. 373 if_exists: 374 Values must be in {'fail', 'change_name', 'overwrite'}. 375 Set what to do if there is a file with same name. 376 auth_header: 377 Auth header to substitute the microservice original 378 at the request (user impersonation). 379 base_filter_skip (list): 380 List of base query filter to be skiped, it is necessary to 381 be superuser to skip base query filters. 382 383 Returns: 384 Returns the file path that recived the file content. 385 386 Raises: 387 PumpWoodForbidden: 388 'storage_object attribute not set for view, file operations 389 are disable'. This indicates that storage for this backend 390 was not configured, so it is not possible to make storage 391 operations, 392 PumpWoodForbidden: 393 'file_field must be set on self.file_fields dictionary'. This 394 indicates that the `file_field` parameter is not listed as 395 a file field on the backend. 396 PumpWoodObjectDoesNotExist: 397 'field [{}] not found or null at object'. This indicates that 398 the file field requested is not present on object fields. 399 PumpWoodObjectDoesNotExist: 400 'Object not found in storage [{}]'. This indicates that the 401 file associated with file_field is not avaiable at the 402 storage. This should not ocorrur, it might have a manual 403 update at the model_class table or manual removal/rename of 404 files on storage. 405 """ 406 base_filter_skip = self._resolve_base_filter_skip( 407 base_filter_skip) 408 request_header = self._check_auth_header(auth_header) 409 410 if if_exists not in ["fail", "change_name", "overwrite"]: 411 raise PumpWoodException( 412 "if_exists must be in ['fail', 'change_name', 'overwrite']") 413 if not os.path.exists(save_path): 414 raise PumpWoodException( 415 "Path to save retrieved file [{}] does not exist".format( 416 save_path)) 417 418 file_path = os.path.join(save_path, file_name) 419 if os.path.isfile(file_path) and if_exists == "change_name": 420 filename, file_extension = os.path.splitext(file_path) 421 too_many_tries = False 422 for i in range(10): 423 new_path = "{filename}__{count}{extension}".format( 424 filename=filename, count=i, 425 extension=file_extension) 426 if not os.path.isfile(new_path): 427 file_path = new_path 428 too_many_tries = True 429 break 430 if not too_many_tries: 431 raise PumpWoodException( 432 ("Too many tries to find a not used file name." + 433 " file_path[{}]".format(file_path))) 434 435 if os.path.isfile(file_path) and if_exists == "fail": 436 raise PumpWoodException( 437 ("if_exists set as 'fail' and there is a file with same" + 438 "name. file_path [{}]").format(file_path)) 439 440 url_str = self._build_retrieve_file_straming_url( 441 model_class=model_class, pk=pk) 442 443 get_url = self.server_url + url_str 444 get_params = { 445 "file-field": file_field, 446 "base_filter_skip": base_filter_skip} 447 dumped_parameters = self._dump_query_parameters(parameters=get_params) 448 with requests.get( 449 get_url, verify=self._verify_ssl, headers=request_header, 450 params=dumped_parameters, 451 timeout=self._default_timeout) as response: 452 self.error_handler(response) 453 with open(file_path, 'wb') as f: 454 for chunk in response.iter_content(chunk_size=8192): 455 if chunk: 456 f.write(chunk) 457 return file_path
Retrieve a file from PumpWood using streaming to retrieve content.
This funcion uses file streaming to retrieve file content, it should be prefered when dealing with large (bigger than 10Mb) files transfer. Using this end-point the file is not loaded on backend memory content is transfered by chucks that are read at the storage and transfered to user.
It will necessarily save the content as a file, there is not the possibility of retrieving the content directly from request.
Arguments:
- model_class: Class of the model to retrieve file.
- pk: Pk of the object associeted file.
- file_field: Field of the file to be downloaded.
- auth_header: Dictionary containing the auth header.
- save_path: Path of the directory to save file.
- file_name: Name of the file, if None it will have same name as saved in PumpWood.
- if_exists: Values must be in {'fail', 'change_name', 'overwrite'}. Set what to do if there is a file with same name.
- auth_header: Auth header to substitute the microservice original at the request (user impersonation).
- base_filter_skip (list): List of base query filter to be skiped, it is necessary to be superuser to skip base query filters.
Returns:
Returns the file path that recived the file content.
Raises:
- PumpWoodForbidden: 'storage_object attribute not set for view, file operations are disable'. This indicates that storage for this backend was not configured, so it is not possible to make storage operations,
- PumpWoodForbidden: 'file_field must be set on self.file_fields dictionary'. This
indicates that the
file_fieldparameter is not listed as a file field on the backend. - PumpWoodObjectDoesNotExist: 'field [{}] not found or null at object'. This indicates that the file field requested is not present on object fields.
- PumpWoodObjectDoesNotExist: 'Object not found in storage [{}]'. This indicates that the file associated with file_field is not avaiable at the storage. This should not ocorrur, it might have a manual update at the model_class table or manual removal/rename of files on storage.
8class ABCSimpleDeleteMicroservice(ABC, PumpWoodMicroServiceBase): 9 """Abstract class for calls at Pumpwood delete end-points.""" 10 11 @staticmethod 12 def _build_delete_request_url(model_class, pk): 13 return "rest/%s/delete/%s/" % (model_class.lower(), pk) 14 15 def delete(self, model_class: str, pk: int, 16 auth_header: dict = None, force_delete: bool = False, 17 base_filter_skip: list[str] | None = None) -> dict: 18 """Send delete request to a PumpWood object. 19 20 Delete (or whatever the PumpWood system have been implemented) the 21 object with the specified pk. 22 23 Args: 24 model_class: 25 Model class to delete the object 26 pk (int): 27 Object pk to be deleted (or whatever the PumpWood system 28 have been implemented). Some model_class with 'deleted' field 29 does not remove the entry, it will flag deleted=True at this 30 cases. Model class with delete=True will be not retrieved 31 by default on `list` and `list_without_pag` end-points. 32 force_delete (bool): 33 If True, the object will be deleted even if it is not 34 soft deleted. 35 auth_header (dict): 36 Auth header to substitute the microservice original 37 at the request (user impersonation). 38 base_filter_skip (list[str]): 39 List of base query filter to be skiped, it is necessary to 40 be superuser to skip base query filters. 41 42 Returns: 43 Returns delete object. 44 45 Raises: 46 PumpWoodObjectDoesNotExist: 47 'Requested object {model_class}[{pk}] not found.' This 48 indicates that the pk was not found in database. 49 """ 50 base_filter_skip = self._resolve_base_filter_skip( 51 base_filter_skip) 52 53 url_str = self._build_delete_request_url(model_class, pk) 54 return self.request_delete( 55 url=url_str, auth_header=auth_header, 56 parameters={ 57 "base_filter_skip": base_filter_skip, 58 "force_delete": force_delete}) 59 60 @staticmethod 61 def _build_remove_file_field(model_class, pk): 62 return "rest/%s/remove-file-field/%s/" % (model_class.lower(), pk) 63 64 def delete_file(self, model_class: str, pk: int, file_field: str, 65 auth_header: dict = None, 66 base_filter_skip: list[str] | None = None) -> bool: 67 """Send delete request to a PumpWood object. 68 69 Delete (or whatever the PumpWood system have been implemented) the 70 object with the specified pk. 71 72 At previous versions this function was `remove_file_field`. An alias 73 is created for backward compatibility. 74 75 Args: 76 model_class: 77 Model class to delete the object 78 pk: 79 Object pk to be deleted (or whatever the PumpWood system 80 have been implemented). 81 file_field: 82 File field to be removed from storage. 83 auth_header: 84 Auth header to substitute the microservice original 85 at the request (user impersonation). 86 base_filter_skip (list[str]): 87 List of base query filter to be skiped, it is necessary to 88 be superuser to skip base query filters. 89 90 Returns: 91 Return True is file was successful removed 92 93 Raises: 94 PumpWoodForbidden: 95 'storage_object attribute not set for view, file operations 96 are disable'. This indicates that storage_object is not 97 associated with view, not allowing it to make storage 98 operations. 99 PumpWoodForbidden: 100 'file_field must be set on self.file_fields dictionary.'. 101 This indicates that the `file_field` was not set as a file 102 field on the backend. 103 PumpWoodObjectDoesNotExist: 104 'File does not exist. File field [{}] is set as None'. 105 This indicates that the object does not exists on storage, 106 it should not occur. It might have been some manual update 107 of the database or at the storage level. 108 """ 109 base_filter_skip = self._resolve_base_filter_skip( 110 base_filter_skip) 111 112 url_str = self._build_remove_file_field(model_class, pk) 113 return self.request_delete( 114 url=url_str, auth_header=auth_header, 115 parameters={ 116 "file-field": file_field, 117 "base_filter_skip": base_filter_skip}) 118 119 # Create an alias for backward compatibility. 120 remove_file_field = delete_file 121 122 @staticmethod 123 def _build_delete_many_request_url(model_class): 124 return "rest/%s/delete/" % (model_class.lower(), ) 125 126 def delete_many(self, model_class: str, filter_dict: None | dict = None, 127 exclude_dict: None | dict = None, 128 force_delete: bool = False, 129 auth_header: dict = None, 130 base_filter_skip: list[str] = None) -> bool: 131 """Remove many objects using query to retrict removal. 132 133 CAUTION It is not possible to undo this operation, model_class 134 this deleted field will be removed from database when using this 135 end-point, different from using delete end-point. 136 137 Args: 138 model_class: 139 Model class to delete the object 140 filter_dict: 141 Dictionary to make filter query. 142 exclude_dict: 143 Dictionary to make exclude query. 144 auth_header: 145 Auth header to substitute the microservice original 146 at the request (user impersonation). 147 base_filter_skip (list): 148 List of base query filter to be skiped, it is necessary to 149 be superuser to skip base query filters. 150 151 Returns: 152 True if delete is ok. 153 154 Raises: 155 PumpWoodObjectDeleteException: 156 Raises error if there is any error when commiting object 157 deletion on database. 158 """ 159 filter_dict = {} if filter_dict is None else filter_dict 160 exclude_dict = {} if exclude_dict is None else exclude_dict 161 base_filter_skip = self._resolve_base_filter_skip( 162 base_filter_skip) 163 164 if force_delete: 165 raise NotImplementedError("Force delete is not implemented yet.") 166 167 url_str = self._build_delete_many_request_url(model_class) 168 return self.request_post( 169 url=url_str, 170 parameters={ 171 'base_filter_skip': base_filter_skip, 172 'force_delete': force_delete}, 173 data={'filter_dict': filter_dict, 'exclude_dict': exclude_dict}, 174 auth_header=auth_header)
Abstract class for calls at Pumpwood delete end-points.
15 def delete(self, model_class: str, pk: int, 16 auth_header: dict = None, force_delete: bool = False, 17 base_filter_skip: list[str] | None = None) -> dict: 18 """Send delete request to a PumpWood object. 19 20 Delete (or whatever the PumpWood system have been implemented) the 21 object with the specified pk. 22 23 Args: 24 model_class: 25 Model class to delete the object 26 pk (int): 27 Object pk to be deleted (or whatever the PumpWood system 28 have been implemented). Some model_class with 'deleted' field 29 does not remove the entry, it will flag deleted=True at this 30 cases. Model class with delete=True will be not retrieved 31 by default on `list` and `list_without_pag` end-points. 32 force_delete (bool): 33 If True, the object will be deleted even if it is not 34 soft deleted. 35 auth_header (dict): 36 Auth header to substitute the microservice original 37 at the request (user impersonation). 38 base_filter_skip (list[str]): 39 List of base query filter to be skiped, it is necessary to 40 be superuser to skip base query filters. 41 42 Returns: 43 Returns delete object. 44 45 Raises: 46 PumpWoodObjectDoesNotExist: 47 'Requested object {model_class}[{pk}] not found.' This 48 indicates that the pk was not found in database. 49 """ 50 base_filter_skip = self._resolve_base_filter_skip( 51 base_filter_skip) 52 53 url_str = self._build_delete_request_url(model_class, pk) 54 return self.request_delete( 55 url=url_str, auth_header=auth_header, 56 parameters={ 57 "base_filter_skip": base_filter_skip, 58 "force_delete": force_delete})
Send delete request to a PumpWood object.
Delete (or whatever the PumpWood system have been implemented) the object with the specified pk.
Arguments:
- model_class: Model class to delete the object
- pk (int): Object pk to be deleted (or whatever the PumpWood system
have been implemented). Some model_class with 'deleted' field
does not remove the entry, it will flag deleted=True at this
cases. Model class with delete=True will be not retrieved
by default on
listandlist_without_pagend-points. - force_delete (bool): If True, the object will be deleted even if it is not soft deleted.
- auth_header (dict): Auth header to substitute the microservice original at the request (user impersonation).
- base_filter_skip (list[str]): List of base query filter to be skiped, it is necessary to be superuser to skip base query filters.
Returns:
Returns delete object.
Raises:
- PumpWoodObjectDoesNotExist: 'Requested object {model_class}[{pk}] not found.' This indicates that the pk was not found in database.
64 def delete_file(self, model_class: str, pk: int, file_field: str, 65 auth_header: dict = None, 66 base_filter_skip: list[str] | None = None) -> bool: 67 """Send delete request to a PumpWood object. 68 69 Delete (or whatever the PumpWood system have been implemented) the 70 object with the specified pk. 71 72 At previous versions this function was `remove_file_field`. An alias 73 is created for backward compatibility. 74 75 Args: 76 model_class: 77 Model class to delete the object 78 pk: 79 Object pk to be deleted (or whatever the PumpWood system 80 have been implemented). 81 file_field: 82 File field to be removed from storage. 83 auth_header: 84 Auth header to substitute the microservice original 85 at the request (user impersonation). 86 base_filter_skip (list[str]): 87 List of base query filter to be skiped, it is necessary to 88 be superuser to skip base query filters. 89 90 Returns: 91 Return True is file was successful removed 92 93 Raises: 94 PumpWoodForbidden: 95 'storage_object attribute not set for view, file operations 96 are disable'. This indicates that storage_object is not 97 associated with view, not allowing it to make storage 98 operations. 99 PumpWoodForbidden: 100 'file_field must be set on self.file_fields dictionary.'. 101 This indicates that the `file_field` was not set as a file 102 field on the backend. 103 PumpWoodObjectDoesNotExist: 104 'File does not exist. File field [{}] is set as None'. 105 This indicates that the object does not exists on storage, 106 it should not occur. It might have been some manual update 107 of the database or at the storage level. 108 """ 109 base_filter_skip = self._resolve_base_filter_skip( 110 base_filter_skip) 111 112 url_str = self._build_remove_file_field(model_class, pk) 113 return self.request_delete( 114 url=url_str, auth_header=auth_header, 115 parameters={ 116 "file-field": file_field, 117 "base_filter_skip": base_filter_skip})
Send delete request to a PumpWood object.
Delete (or whatever the PumpWood system have been implemented) the object with the specified pk.
At previous versions this function was remove_file_field. An alias
is created for backward compatibility.
Arguments:
- model_class: Model class to delete the object
- pk: Object pk to be deleted (or whatever the PumpWood system have been implemented).
- file_field: File field to be removed from storage.
- auth_header: Auth header to substitute the microservice original at the request (user impersonation).
- base_filter_skip (list[str]): List of base query filter to be skiped, it is necessary to be superuser to skip base query filters.
Returns:
Return True is file was successful removed
Raises:
- PumpWoodForbidden: 'storage_object attribute not set for view, file operations are disable'. This indicates that storage_object is not associated with view, not allowing it to make storage operations.
- PumpWoodForbidden: 'file_field must be set on self.file_fields dictionary.'.
This indicates that the
file_fieldwas not set as a file field on the backend. - PumpWoodObjectDoesNotExist: 'File does not exist. File field [{}] is set as None'. This indicates that the object does not exists on storage, it should not occur. It might have been some manual update of the database or at the storage level.
64 def delete_file(self, model_class: str, pk: int, file_field: str, 65 auth_header: dict = None, 66 base_filter_skip: list[str] | None = None) -> bool: 67 """Send delete request to a PumpWood object. 68 69 Delete (or whatever the PumpWood system have been implemented) the 70 object with the specified pk. 71 72 At previous versions this function was `remove_file_field`. An alias 73 is created for backward compatibility. 74 75 Args: 76 model_class: 77 Model class to delete the object 78 pk: 79 Object pk to be deleted (or whatever the PumpWood system 80 have been implemented). 81 file_field: 82 File field to be removed from storage. 83 auth_header: 84 Auth header to substitute the microservice original 85 at the request (user impersonation). 86 base_filter_skip (list[str]): 87 List of base query filter to be skiped, it is necessary to 88 be superuser to skip base query filters. 89 90 Returns: 91 Return True is file was successful removed 92 93 Raises: 94 PumpWoodForbidden: 95 'storage_object attribute not set for view, file operations 96 are disable'. This indicates that storage_object is not 97 associated with view, not allowing it to make storage 98 operations. 99 PumpWoodForbidden: 100 'file_field must be set on self.file_fields dictionary.'. 101 This indicates that the `file_field` was not set as a file 102 field on the backend. 103 PumpWoodObjectDoesNotExist: 104 'File does not exist. File field [{}] is set as None'. 105 This indicates that the object does not exists on storage, 106 it should not occur. It might have been some manual update 107 of the database or at the storage level. 108 """ 109 base_filter_skip = self._resolve_base_filter_skip( 110 base_filter_skip) 111 112 url_str = self._build_remove_file_field(model_class, pk) 113 return self.request_delete( 114 url=url_str, auth_header=auth_header, 115 parameters={ 116 "file-field": file_field, 117 "base_filter_skip": base_filter_skip})
Send delete request to a PumpWood object.
Delete (or whatever the PumpWood system have been implemented) the object with the specified pk.
At previous versions this function was remove_file_field. An alias
is created for backward compatibility.
Arguments:
- model_class: Model class to delete the object
- pk: Object pk to be deleted (or whatever the PumpWood system have been implemented).
- file_field: File field to be removed from storage.
- auth_header: Auth header to substitute the microservice original at the request (user impersonation).
- base_filter_skip (list[str]): List of base query filter to be skiped, it is necessary to be superuser to skip base query filters.
Returns:
Return True is file was successful removed
Raises:
- PumpWoodForbidden: 'storage_object attribute not set for view, file operations are disable'. This indicates that storage_object is not associated with view, not allowing it to make storage operations.
- PumpWoodForbidden: 'file_field must be set on self.file_fields dictionary.'.
This indicates that the
file_fieldwas not set as a file field on the backend. - PumpWoodObjectDoesNotExist: 'File does not exist. File field [{}] is set as None'. This indicates that the object does not exists on storage, it should not occur. It might have been some manual update of the database or at the storage level.
126 def delete_many(self, model_class: str, filter_dict: None | dict = None, 127 exclude_dict: None | dict = None, 128 force_delete: bool = False, 129 auth_header: dict = None, 130 base_filter_skip: list[str] = None) -> bool: 131 """Remove many objects using query to retrict removal. 132 133 CAUTION It is not possible to undo this operation, model_class 134 this deleted field will be removed from database when using this 135 end-point, different from using delete end-point. 136 137 Args: 138 model_class: 139 Model class to delete the object 140 filter_dict: 141 Dictionary to make filter query. 142 exclude_dict: 143 Dictionary to make exclude query. 144 auth_header: 145 Auth header to substitute the microservice original 146 at the request (user impersonation). 147 base_filter_skip (list): 148 List of base query filter to be skiped, it is necessary to 149 be superuser to skip base query filters. 150 151 Returns: 152 True if delete is ok. 153 154 Raises: 155 PumpWoodObjectDeleteException: 156 Raises error if there is any error when commiting object 157 deletion on database. 158 """ 159 filter_dict = {} if filter_dict is None else filter_dict 160 exclude_dict = {} if exclude_dict is None else exclude_dict 161 base_filter_skip = self._resolve_base_filter_skip( 162 base_filter_skip) 163 164 if force_delete: 165 raise NotImplementedError("Force delete is not implemented yet.") 166 167 url_str = self._build_delete_many_request_url(model_class) 168 return self.request_post( 169 url=url_str, 170 parameters={ 171 'base_filter_skip': base_filter_skip, 172 'force_delete': force_delete}, 173 data={'filter_dict': filter_dict, 'exclude_dict': exclude_dict}, 174 auth_header=auth_header)
Remove many objects using query to retrict removal.
CAUTION It is not possible to undo this operation, model_class this deleted field will be removed from database when using this end-point, different from using delete end-point.
Arguments:
- model_class: Model class to delete the object
- filter_dict: Dictionary to make filter query.
- exclude_dict: Dictionary to make exclude query.
- auth_header: Auth header to substitute the microservice original at the request (user impersonation).
- base_filter_skip (list): List of base query filter to be skiped, it is necessary to be superuser to skip base query filters.
Returns:
True if delete is ok.
Raises:
- PumpWoodObjectDeleteException: Raises error if there is any error when commiting object deletion on database.
14class ABCSimpleSaveMicroservice(ABC, PumpWoodMicroServiceBase): 15 """Abstract class for parallel calls at Pumpwood end-points.""" 16 17 @staticmethod 18 def _build_save_url(model_class): 19 return "rest/%s/save/" % (model_class.lower()) 20 21 def save(self, obj_dict: dict, files: dict = None, 22 auth_header: dict = None, fields: list = None, 23 default_fields: bool = False, foreign_key_fields: bool = False, 24 related_fields: bool = False, base_filter_skip: list = None, 25 upsert: bool = False) -> dict[str, Any]: 26 """Save or Update a new object. 27 28 Function to save or update a new model_class object. If obj_dict['pk'] 29 is None or not defined a new object will be created. The obj 30 model class is defided at obj_dict['model_class'] and if not defined an 31 PumpWoodObjectSavingException will be raised. 32 33 If files argument is set, request will be transfered using a multipart 34 request file files mapping file key to file field on backend. 35 36 Args: 37 obj_dict: 38 Model data dictionary. It must have 'model_class' 39 key and if 'pk' key is not defined a new object will 40 be created, else object with pk will be updated. 41 files: 42 A dictionary of files to be added to as a multi-part 43 post request. File must be passed as a file object with read 44 bytes. 45 auth_header: 46 Auth header to substitute the microservice original 47 at the request (user impersonation). 48 fields: 49 Set the fields to be returned by the list end-point. 50 default_fields: 51 Boolean, if true and fields arguments None will return the 52 default fields set for list by the backend. 53 foreign_key_fields: 54 Return forenging key objects. It will return the fk 55 corresponding object. Ex: `created_by_id` reference to 56 a user `model_class` the correspondent to User will be 57 returned at `created_by`. 58 related_fields: 59 Return related fields objects. Related field objects are 60 objects that have a forenging key associated with this 61 model_class, results will be returned as a list of 62 dictionaries usually in a field with `_set` at end. 63 Returning related_fields consume backend resorces, use 64 carefully. 65 base_filter_skip (list[str]): 66 List of base query filter to be skiped, it is necessary to 67 be superuser to skip base query filters. 68 upsert (bool): 69 Perform an upsert operation, if the object associated with the 70 pk is not found, it will be inserted on the database. 71 72 Returns: 73 Return updated/created object data. 74 75 Raises: 76 PumpWoodObjectSavingException: 77 'To save an object obj_dict must have model_class defined.' 78 This indicates that the obj_dict must have key `model_class` 79 indicating model class of the object that will be 80 updated/created. 81 PumpWoodObjectDoesNotExist: 82 'Requested object {model_class}[{pk}] not found.'. This 83 indicates that the pk passed on obj_dict was not found on 84 backend database. 85 PumpWoodIntegrityError: 86 Error raised when IntegrityError is raised on database. This 87 might ocorrur when saving objects that does not respect 88 uniqueness restriction on database or other IntegrityError 89 like removal of foreign keys with related data. 90 PumpWoodObjectSavingException: 91 Return error at object validation on de-serializing the 92 object or files with unexpected extensions. 93 """ 94 base_filter_skip = self._resolve_base_filter_skip( 95 base_filter_skip) 96 97 model_class = obj_dict.get('model_class') 98 if model_class is None: 99 raise PumpWoodObjectSavingException( 100 'To save an object obj_dict must have model_class defined.') 101 102 url_str = self._build_save_url(model_class) 103 parameters = { 104 "fields": fields, "default_fields": default_fields, 105 "foreign_key_fields": foreign_key_fields, 106 "related_fields": related_fields, 107 "base_filter_skip": base_filter_skip, 108 "upsert": upsert} 109 return self.request_post( 110 url=url_str, data=obj_dict, parameters=parameters, files=files, 111 auth_header=auth_header) 112 113 @staticmethod 114 def _build_save_streaming_file_url(model_class, pk): 115 return "rest/{model_class}/save-file-streaming/{pk}/".format( 116 model_class=model_class.lower(), pk=pk) 117 118 def save_streaming_file(self, model_class: str, pk: int, file_field: str, 119 file: io.BufferedReader, file_name: str = None, 120 auth_header: dict = None, 121 fields: list = None, default_fields: bool = False, 122 foreign_key_fields: bool = False, 123 related_fields: bool = False, 124 base_filter_skip: list = None) -> str: 125 """Stream file to PumpWood. 126 127 Use streaming to transfer a file content to Pumpwood storage, this 128 end-point is prefered when transmiting files bigger than 10Mb. It 129 is necessary to have the object created before the file transfer. 130 131 Args: 132 model_class: 133 Model class of the object. 134 pk: 135 pk of the object. 136 file_field: 137 File field that will receive file stream. 138 file: 139 File to upload as a file object with read bytes option. 140 auth_header: 141 Auth header to substitute the microservice original 142 at the request (user impersonation). 143 file_name: 144 Name of the file, if not set it will be saved as 145 {pk}__{file_field}.{extension at permited extension} 146 fields: 147 Set the fields to be returned by the list end-point. 148 default_fields: 149 Boolean, if true and fields arguments None will return the 150 default fields set for list by the backend. 151 foreign_key_fields: 152 Return forenging key objects. It will return the fk 153 corresponding object. Ex: `created_by_id` reference to 154 a user `model_class` the correspondent to User will be 155 returned at `created_by`. 156 related_fields: 157 Return related fields objects. Related field objects are 158 objects that have a forenging key associated with this 159 model_class, results will be returned as a list of 160 dictionaries usually in a field with `_set` at end. 161 Returning related_fields consume backend resorces, use 162 carefully. 163 base_filter_skip (list[str]): 164 List of base query filter to be skiped, it is necessary to 165 be superuser to skip base query filters. 166 167 Returns: 168 Return the file name associated with data at the storage. 169 170 Raises: 171 PumpWoodForbidden: 172 'file_field must be set on self.file_fields dictionary'. This 173 indicates that the `file_field` passed is not associated 174 with a file field on the backend. 175 PumpWoodException: 176 'Saved bytes in streaming [{}] differ from file bytes [{}].'. 177 This indicates that there was an error when transfering data 178 to storage, the file bytes and transfered bytes does not 179 match. 180 """ 181 request_header = self._check_auth_header(auth_header=auth_header) 182 request_header["Content-Type"] = "application/octet-stream" 183 post_url = self.server_url + self._build_save_streaming_file_url( 184 model_class=model_class, pk=pk) 185 base_filter_skip = self._resolve_base_filter_skip( 186 base_filter_skip) 187 188 parameters = { 189 "fields": fields, "default_fields": default_fields, 190 "foreign_key_fields": foreign_key_fields, 191 "related_fields": related_fields, "file_field": file_field, 192 "base_filter_skip": base_filter_skip} 193 if file_name is not None: 194 parameters["file_name"] = file_name 195 196 dumped_parameters = self._dump_query_parameters(parameters=parameters) 197 response = requests.post( 198 url=post_url, data=file, params=dumped_parameters, 199 verify=self._verify_ssl, headers=request_header, stream=True, 200 timeout=self._default_timeout) 201 202 file_last_bite = file.tell() 203 self.error_handler(response) 204 json_response = self.angular_json(response) 205 206 if file_last_bite != json_response["bytes_uploaded"]: 207 template = ( 208 "Saved bytes in streaming [{}] differ from file " + 209 "bites [{}].") 210 raise PumpWoodException( 211 template.format( 212 json_response["bytes_uploaded"], file_last_bite)) 213 return json_response["file_path"] 214 215 @staticmethod 216 def _build_bulk_save_url(model_class: str): 217 return "rest/%s/bulk-save/" % (model_class.lower(),) 218 219 def bulk_save(self, model_class: str, data_to_save: list | pd.DataFrame, 220 auth_header: dict = None, 221 base_filter_skip: list = None) -> dict: 222 """Save a list of objects with one request. 223 224 It is used with a unique call save many objects at the same time. It 225 is necessary that the end-point is able to receive bulk save requests 226 and all objects been of the same model class. 227 228 Args: 229 model_class: 230 Data model class. 231 data_to_save: 232 A list of objects to be saved. 233 base_filter_skip (list[str]): 234 List of base query filter to be skiped, it is necessary to 235 be superuser to skip base query filters. 236 auth_header: 237 Auth header to substitute the microservice original 238 at the request (user impersonation). 239 240 Returns: 241 A dictinary with `saved_count` as key indicating the number of 242 objects that were saved in database. 243 244 Raises: 245 PumpWoodException: 246 'Expected columns and data columns do not match: Expected 247 columns: {expected} Data columns: {data_cols}'. Indicates 248 that the expected fields of the object were not met at the 249 objects passed to save. 250 PumpWoodException: 251 Other sqlalchemy and psycopg2 errors not associated with 252 IntegrityError. 253 PumpWoodException: 254 'Bulk save not avaiable.'. Indicates that Bulk save end-point 255 was not configured for this model_class. 256 PumpWoodIntegrityError: 257 Raise integrity errors from sqlalchemy and psycopg2. Usually 258 associated with uniqueness of some column. 259 """ 260 if len(data_to_save) == 0: 261 return False 262 263 base_filter_skip = self._resolve_base_filter_skip( 264 base_filter_skip) 265 url_str = self._build_bulk_save_url(model_class=model_class) 266 return self.request_post( 267 url=url_str, data=data_to_save, 268 parameters={"base_filter_skip": base_filter_skip}, 269 auth_header=auth_header)
Abstract class for parallel calls at Pumpwood end-points.
21 def save(self, obj_dict: dict, files: dict = None, 22 auth_header: dict = None, fields: list = None, 23 default_fields: bool = False, foreign_key_fields: bool = False, 24 related_fields: bool = False, base_filter_skip: list = None, 25 upsert: bool = False) -> dict[str, Any]: 26 """Save or Update a new object. 27 28 Function to save or update a new model_class object. If obj_dict['pk'] 29 is None or not defined a new object will be created. The obj 30 model class is defided at obj_dict['model_class'] and if not defined an 31 PumpWoodObjectSavingException will be raised. 32 33 If files argument is set, request will be transfered using a multipart 34 request file files mapping file key to file field on backend. 35 36 Args: 37 obj_dict: 38 Model data dictionary. It must have 'model_class' 39 key and if 'pk' key is not defined a new object will 40 be created, else object with pk will be updated. 41 files: 42 A dictionary of files to be added to as a multi-part 43 post request. File must be passed as a file object with read 44 bytes. 45 auth_header: 46 Auth header to substitute the microservice original 47 at the request (user impersonation). 48 fields: 49 Set the fields to be returned by the list end-point. 50 default_fields: 51 Boolean, if true and fields arguments None will return the 52 default fields set for list by the backend. 53 foreign_key_fields: 54 Return forenging key objects. It will return the fk 55 corresponding object. Ex: `created_by_id` reference to 56 a user `model_class` the correspondent to User will be 57 returned at `created_by`. 58 related_fields: 59 Return related fields objects. Related field objects are 60 objects that have a forenging key associated with this 61 model_class, results will be returned as a list of 62 dictionaries usually in a field with `_set` at end. 63 Returning related_fields consume backend resorces, use 64 carefully. 65 base_filter_skip (list[str]): 66 List of base query filter to be skiped, it is necessary to 67 be superuser to skip base query filters. 68 upsert (bool): 69 Perform an upsert operation, if the object associated with the 70 pk is not found, it will be inserted on the database. 71 72 Returns: 73 Return updated/created object data. 74 75 Raises: 76 PumpWoodObjectSavingException: 77 'To save an object obj_dict must have model_class defined.' 78 This indicates that the obj_dict must have key `model_class` 79 indicating model class of the object that will be 80 updated/created. 81 PumpWoodObjectDoesNotExist: 82 'Requested object {model_class}[{pk}] not found.'. This 83 indicates that the pk passed on obj_dict was not found on 84 backend database. 85 PumpWoodIntegrityError: 86 Error raised when IntegrityError is raised on database. This 87 might ocorrur when saving objects that does not respect 88 uniqueness restriction on database or other IntegrityError 89 like removal of foreign keys with related data. 90 PumpWoodObjectSavingException: 91 Return error at object validation on de-serializing the 92 object or files with unexpected extensions. 93 """ 94 base_filter_skip = self._resolve_base_filter_skip( 95 base_filter_skip) 96 97 model_class = obj_dict.get('model_class') 98 if model_class is None: 99 raise PumpWoodObjectSavingException( 100 'To save an object obj_dict must have model_class defined.') 101 102 url_str = self._build_save_url(model_class) 103 parameters = { 104 "fields": fields, "default_fields": default_fields, 105 "foreign_key_fields": foreign_key_fields, 106 "related_fields": related_fields, 107 "base_filter_skip": base_filter_skip, 108 "upsert": upsert} 109 return self.request_post( 110 url=url_str, data=obj_dict, parameters=parameters, files=files, 111 auth_header=auth_header)
Save or Update a new object.
Function to save or update a new model_class object. If obj_dict['pk'] is None or not defined a new object will be created. The obj model class is defided at obj_dict['model_class'] and if not defined an PumpWoodObjectSavingException will be raised.
If files argument is set, request will be transfered using a multipart request file files mapping file key to file field on backend.
Arguments:
- obj_dict: Model data dictionary. It must have 'model_class' key and if 'pk' key is not defined a new object will be created, else object with pk will be updated.
- files: A dictionary of files to be added to as a multi-part post request. File must be passed as a file object with read bytes.
- auth_header: Auth header to substitute the microservice original at the request (user impersonation).
- fields: Set the fields to be returned by the list end-point.
- default_fields: Boolean, if true and fields arguments None will return the default fields set for list by the backend.
- foreign_key_fields: Return forenging key objects. It will return the fk
corresponding object. Ex:
created_by_idreference to a usermodel_classthe correspondent to User will be returned atcreated_by. - related_fields: Return related fields objects. Related field objects are
objects that have a forenging key associated with this
model_class, results will be returned as a list of
dictionaries usually in a field with
_setat end. Returning related_fields consume backend resorces, use carefully. - base_filter_skip (list[str]): List of base query filter to be skiped, it is necessary to be superuser to skip base query filters.
- upsert (bool): Perform an upsert operation, if the object associated with the pk is not found, it will be inserted on the database.
Returns:
Return updated/created object data.
Raises:
- PumpWoodObjectSavingException: 'To save an object obj_dict must have model_class defined.'
This indicates that the obj_dict must have key
model_classindicating model class of the object that will be updated/created. - PumpWoodObjectDoesNotExist: 'Requested object {model_class}[{pk}] not found.'. This indicates that the pk passed on obj_dict was not found on backend database.
- PumpWoodIntegrityError: Error raised when IntegrityError is raised on database. This might ocorrur when saving objects that does not respect uniqueness restriction on database or other IntegrityError like removal of foreign keys with related data.
- PumpWoodObjectSavingException: Return error at object validation on de-serializing the object or files with unexpected extensions.
118 def save_streaming_file(self, model_class: str, pk: int, file_field: str, 119 file: io.BufferedReader, file_name: str = None, 120 auth_header: dict = None, 121 fields: list = None, default_fields: bool = False, 122 foreign_key_fields: bool = False, 123 related_fields: bool = False, 124 base_filter_skip: list = None) -> str: 125 """Stream file to PumpWood. 126 127 Use streaming to transfer a file content to Pumpwood storage, this 128 end-point is prefered when transmiting files bigger than 10Mb. It 129 is necessary to have the object created before the file transfer. 130 131 Args: 132 model_class: 133 Model class of the object. 134 pk: 135 pk of the object. 136 file_field: 137 File field that will receive file stream. 138 file: 139 File to upload as a file object with read bytes option. 140 auth_header: 141 Auth header to substitute the microservice original 142 at the request (user impersonation). 143 file_name: 144 Name of the file, if not set it will be saved as 145 {pk}__{file_field}.{extension at permited extension} 146 fields: 147 Set the fields to be returned by the list end-point. 148 default_fields: 149 Boolean, if true and fields arguments None will return the 150 default fields set for list by the backend. 151 foreign_key_fields: 152 Return forenging key objects. It will return the fk 153 corresponding object. Ex: `created_by_id` reference to 154 a user `model_class` the correspondent to User will be 155 returned at `created_by`. 156 related_fields: 157 Return related fields objects. Related field objects are 158 objects that have a forenging key associated with this 159 model_class, results will be returned as a list of 160 dictionaries usually in a field with `_set` at end. 161 Returning related_fields consume backend resorces, use 162 carefully. 163 base_filter_skip (list[str]): 164 List of base query filter to be skiped, it is necessary to 165 be superuser to skip base query filters. 166 167 Returns: 168 Return the file name associated with data at the storage. 169 170 Raises: 171 PumpWoodForbidden: 172 'file_field must be set on self.file_fields dictionary'. This 173 indicates that the `file_field` passed is not associated 174 with a file field on the backend. 175 PumpWoodException: 176 'Saved bytes in streaming [{}] differ from file bytes [{}].'. 177 This indicates that there was an error when transfering data 178 to storage, the file bytes and transfered bytes does not 179 match. 180 """ 181 request_header = self._check_auth_header(auth_header=auth_header) 182 request_header["Content-Type"] = "application/octet-stream" 183 post_url = self.server_url + self._build_save_streaming_file_url( 184 model_class=model_class, pk=pk) 185 base_filter_skip = self._resolve_base_filter_skip( 186 base_filter_skip) 187 188 parameters = { 189 "fields": fields, "default_fields": default_fields, 190 "foreign_key_fields": foreign_key_fields, 191 "related_fields": related_fields, "file_field": file_field, 192 "base_filter_skip": base_filter_skip} 193 if file_name is not None: 194 parameters["file_name"] = file_name 195 196 dumped_parameters = self._dump_query_parameters(parameters=parameters) 197 response = requests.post( 198 url=post_url, data=file, params=dumped_parameters, 199 verify=self._verify_ssl, headers=request_header, stream=True, 200 timeout=self._default_timeout) 201 202 file_last_bite = file.tell() 203 self.error_handler(response) 204 json_response = self.angular_json(response) 205 206 if file_last_bite != json_response["bytes_uploaded"]: 207 template = ( 208 "Saved bytes in streaming [{}] differ from file " + 209 "bites [{}].") 210 raise PumpWoodException( 211 template.format( 212 json_response["bytes_uploaded"], file_last_bite)) 213 return json_response["file_path"]
Stream file to PumpWood.
Use streaming to transfer a file content to Pumpwood storage, this end-point is prefered when transmiting files bigger than 10Mb. It is necessary to have the object created before the file transfer.
Arguments:
- model_class: Model class of the object.
- pk: pk of the object.
- file_field: File field that will receive file stream.
- file: File to upload as a file object with read bytes option.
- auth_header: Auth header to substitute the microservice original at the request (user impersonation).
- file_name: Name of the file, if not set it will be saved as {pk}__{file_field}.{extension at permited extension}
- fields: Set the fields to be returned by the list end-point.
- default_fields: Boolean, if true and fields arguments None will return the default fields set for list by the backend.
- foreign_key_fields: Return forenging key objects. It will return the fk
corresponding object. Ex:
created_by_idreference to a usermodel_classthe correspondent to User will be returned atcreated_by. - related_fields: Return related fields objects. Related field objects are
objects that have a forenging key associated with this
model_class, results will be returned as a list of
dictionaries usually in a field with
_setat end. Returning related_fields consume backend resorces, use carefully. - base_filter_skip (list[str]): List of base query filter to be skiped, it is necessary to be superuser to skip base query filters.
Returns:
Return the file name associated with data at the storage.
Raises:
- PumpWoodForbidden: 'file_field must be set on self.file_fields dictionary'. This
indicates that the
file_fieldpassed is not associated with a file field on the backend. - PumpWoodException: 'Saved bytes in streaming [{}] differ from file bytes [{}].'. This indicates that there was an error when transfering data to storage, the file bytes and transfered bytes does not match.
219 def bulk_save(self, model_class: str, data_to_save: list | pd.DataFrame, 220 auth_header: dict = None, 221 base_filter_skip: list = None) -> dict: 222 """Save a list of objects with one request. 223 224 It is used with a unique call save many objects at the same time. It 225 is necessary that the end-point is able to receive bulk save requests 226 and all objects been of the same model class. 227 228 Args: 229 model_class: 230 Data model class. 231 data_to_save: 232 A list of objects to be saved. 233 base_filter_skip (list[str]): 234 List of base query filter to be skiped, it is necessary to 235 be superuser to skip base query filters. 236 auth_header: 237 Auth header to substitute the microservice original 238 at the request (user impersonation). 239 240 Returns: 241 A dictinary with `saved_count` as key indicating the number of 242 objects that were saved in database. 243 244 Raises: 245 PumpWoodException: 246 'Expected columns and data columns do not match: Expected 247 columns: {expected} Data columns: {data_cols}'. Indicates 248 that the expected fields of the object were not met at the 249 objects passed to save. 250 PumpWoodException: 251 Other sqlalchemy and psycopg2 errors not associated with 252 IntegrityError. 253 PumpWoodException: 254 'Bulk save not avaiable.'. Indicates that Bulk save end-point 255 was not configured for this model_class. 256 PumpWoodIntegrityError: 257 Raise integrity errors from sqlalchemy and psycopg2. Usually 258 associated with uniqueness of some column. 259 """ 260 if len(data_to_save) == 0: 261 return False 262 263 base_filter_skip = self._resolve_base_filter_skip( 264 base_filter_skip) 265 url_str = self._build_bulk_save_url(model_class=model_class) 266 return self.request_post( 267 url=url_str, data=data_to_save, 268 parameters={"base_filter_skip": base_filter_skip}, 269 auth_header=auth_header)
Save a list of objects with one request.
It is used with a unique call save many objects at the same time. It is necessary that the end-point is able to receive bulk save requests and all objects been of the same model class.
Arguments:
- model_class: Data model class.
- data_to_save: A list of objects to be saved.
- base_filter_skip (list[str]): List of base query filter to be skiped, it is necessary to be superuser to skip base query filters.
- auth_header: Auth header to substitute the microservice original at the request (user impersonation).
Returns:
A dictinary with
saved_countas key indicating the number of objects that were saved in database.
Raises:
- PumpWoodException: 'Expected columns and data columns do not match: Expected columns: {expected} Data columns: {data_cols}'. Indicates that the expected fields of the object were not met at the objects passed to save.
- PumpWoodException: Other sqlalchemy and psycopg2 errors not associated with IntegrityError.
- PumpWoodException: 'Bulk save not avaiable.'. Indicates that Bulk save end-point was not configured for this model_class.
- PumpWoodIntegrityError: Raise integrity errors from sqlalchemy and psycopg2. Usually associated with uniqueness of some column.
15class ABCSimpleListMicroservice(ABC, PumpWoodMicroServiceBase): 16 """Abstract class for parallel calls at Pumpwood end-points.""" 17 18 @staticmethod 19 def _build_list_url(model_class: str): 20 return "rest/%s/list/" % (model_class.lower(),) 21 22 def list(self, model_class: str, filter_dict: dict = None, 23 exclude_dict: dict = None, order_by: list = None, 24 auth_header: dict = None, fields: list = None, 25 default_fields: bool = False, limit: int = None, 26 foreign_key_fields: bool = False, 27 base_filter_skip: list[str] = None, 28 as_dataframe: bool = False, 29 **kwargs) -> List[dict]: 30 """List objects with pagination. 31 32 List end-point (resumed data) of PumpWood like systems, 33 results will be paginated. To get next pag, send all recived pk at 34 exclude dict (ex.: `exclude_dict={pk__in: [1,2,...,30]}`). 35 36 It is possible to return foreign keys objects associated with 37 `model_class`. Use this with carefull since increase the backend 38 infrastructure consumption, each object is a retrieve call per 39 foreign key (otimization in progress). 40 41 It is possible to use diferent operators using `__` after the name 42 of the field, some of the operators avaiable: 43 44 ### General operators 45 - **__eq:** Check if the value is the same, same results if no 46 operator is passed. 47 - **__gt:** Check if value is greter then argument. 48 - **__lt:** Check if value is less then argument. 49 - **__gte:** Check if value is greter or equal then argument. 50 - **__lte:** Check if value is less or equal then argument. 51 - **__in:** Check if value is at a list, the argument of this operator 52 must be a list. 53 54 ### Text field operators 55 - **__contains:** Check if value contains a string. It is case and 56 accent sensitive. 57 - **__icontains:** Check if a values contains a string, It is case 58 insensitive and accent sensitive. 59 - **__unaccent_icontains:** Check if a values contains a string, It is 60 case insensitive and accent insensitive (consider a, à , á, ã, ... 61 the same). 62 - **__exact:** Same as __eq or not setting operator. 63 - **__iexact:** Same as __eq, but case insensitive and 64 accent sensitive. 65 - **__unaccent_iexact:** Same as __eq, but case insensitive and 66 accent insensitive. 67 - **__startswith:** Check if the value stats with a sub-string. 68 Case sensitive and accent sensitive. 69 - **__istartswith:** Check if the value stats with a sub-string. 70 Case insensitive and accent sensitive. 71 - **__unaccent_istartswith:** Check if the value stats with a 72 sub-string. Case insensitive and accent insensitive. 73 - **__endswith:** Check if the value ends with a sub-string. Case 74 sensitive and accent sensitive. 75 - **__iendswith:** Check if the value ends with a sub-string. Case 76 insensitive and accent sensitive. 77 - **__unaccent_iendswith:** Check if the value ends with a sub-string. 78 Case insensitive and accent insensitive. 79 80 ### Null operators 81 - **__isnull:** Check if field is null, it uses as argument a `boolean` 82 value false will return all non NULL values and true will return 83 NULL values. 84 85 ### Date and datetime operators: 86 - **__range:** Receive as argument a list of two elements and return 87 objects that field dates are between those values. 88 - **__year:** Return object that date field value year is equal to 89 argument. 90 - **__month:** Return object that date field value month is equal to 91 argument. 92 - **__day:** Return object that date field value day is equal to 93 argument. 94 95 ### Dictionary fields operators: 96 - **__json_contained_by:** 97 Uses the function [contained_by](https://docs.sqlalchemy.org/en/20/dialects/postgresql.html#sqlalchemy.dialects.postgresql.JSONB.Comparator.contained_by) 98 from SQLAlchemy to test if keys are a proper subset of the keys of 99 the argument jsonb expression (extracted from SQLAlchemy). The 100 argument is a list. 101 - **__json_has_any:** 102 Uses the function [has_any](https://docs.sqlalchemy.org/en/20/dialects/postgresql.html#sqlalchemy.dialects.postgresql.JSONB.Comparator.has_any) 103 from SQLAlchemy to test for presence of a key. Note that the key 104 may be a SQLA expression. (extracted from SQLAlchemy). The 105 argument is a list. 106 - **__json_has_key:** 107 Uses the function [has_key](https://docs.sqlalchemy.org/en/20/dialects/postgresql.html#sqlalchemy.dialects.postgresql.JSONB.Comparator.has_key) 108 from SQLAlchemy to Test for presence of a key. Note that the key 109 may be a SQLA expression. The argument is a str. 110 111 ### Text similarity operators 112 To use similariry querys on Postgres it is necessary to `pg_trgm` be 113 instaled on server. Check [oficial documentation] 114 (https://www.postgresql.org/docs/current/pgtrgm.html). 115 116 - **__similarity:** Check if two strings are similar uses the `%` 117 operador. 118 - **__word_similar_left:** Check if two strings are similar uses the 119 `<%` operador. 120 - **__word_similar_right:** Check if two strings are similar uses the 121 `%>` operador. 122 - **__strict_word__similar_left:** Check if two strings are similar 123 uses the `<<%` operador. 124 - **__strict_word__similar_right:** Check if two strings are similar 125 uses the `%>>` operador. 126 127 Some usage examples: 128 ```python 129 # Return the first 3 results ordered decreasing acording to `time` and 130 # them ordered by `modeling_unit_id`. Results must have time greater 131 # or equal to 2017-01-01 and less or equal to 2017-06-01. It also 132 # must have attribute_id equal to 6 and not contains modeling_unit_id 133 # 3 or 4. 134 microservice.list( 135 model_class="DatabaseVariable", 136 filter_dict={ 137 "time__gte": "2017-01-01 00:00:00", 138 "time__lte": "2017-06-01 00:00:00", 139 "attribute_id": 6}, 140 exclude_dict={ 141 "modeling_unit_id__in": [3, 4]}, 142 order_by=["-time", "modeling_unit_id"], 143 limit=3, 144 fields=["pk", "model_class", "time", "modeling_unit_id", "value"]) 145 146 # Return all elements that dimensions field has a key type with 147 # value contains `selling` insensitive to case and accent. 148 microservice.list( 149 model_class="DatabaseAttribute", 150 filter_dict={ 151 "dimensions->type__unaccent_icontains": "selling"}) 152 ``` 153 154 Args: 155 model_class: 156 Model class of the end-point 157 filter_dict: 158 Filter dict to be used at the query. Filter elements from query 159 return that satifies all statements of the dictonary. 160 exclude_dict: 161 Exclude dict to be used at the query. Remove elements from 162 query return that satifies all statements of the dictonary. 163 order_by: Order results acording to list of strings 164 correspondent to fields. It is possible to use '-' at the 165 begginng of the field name for reverse ordering. Ex.: 166 ['description'] for accendent ordering and ['-description'] 167 for descendent ordering. 168 auth_header: 169 Auth header to substitute the microservice original 170 at the request (user impersonation). 171 fields (list): 172 Set the fields to be returned by the list end-point. 173 default_fields (bool): 174 Boolean, if true and fields arguments None will return the 175 default fields set for list by the backend. 176 limit (int): 177 Set the limit of elements of the returned query. By default, 178 backend usually return 50 elements. 179 foreign_key_fields (bool): 180 Return forenging key objects. It will return the fk 181 corresponding object. Ex: `created_by_id` reference to 182 a user `model_class` the correspondent to User will be 183 returned at `created_by`. 184 base_filter_skip (list): 185 List of base query filter to be skiped, it is necessary to 186 be superuser to skip base query filters. 187 as_dataframe (bool): 188 Return data as dataframe and set the columns acording to 189 fields if set. 190 **kwargs: 191 Other parameters for compatibility. 192 193 Returns: 194 Containing objects serialized by list Serializer. 195 196 Raises: 197 No especific raises. 198 """ # NOQA 199 filter_dict = {} if filter_dict is None else filter_dict 200 exclude_dict = {} if exclude_dict is None else exclude_dict 201 order_by = [] if order_by is None else order_by 202 203 base_filter_skip = self._resolve_base_filter_skip( 204 base_filter_skip) 205 206 url_str = self._build_list_url(model_class) 207 post_data = { 208 'filter_dict': filter_dict, 'exclude_dict': exclude_dict, 209 'order_by': order_by, 'default_fields': default_fields, 210 'limit': limit, 'foreign_key_fields': foreign_key_fields} 211 if fields is not None: 212 post_data["fields"] = fields 213 return_data = self.request_post( 214 url=url_str, data=post_data, 215 parameters={'base_filter_skip': base_filter_skip}, 216 auth_header=auth_header) 217 if not as_dataframe: 218 return return_data 219 else: 220 # Return the results as a dataframe using the group_by columns 221 # and the columns created at aggregation to ensure that 222 # even empty results would have the correct columns 223 return pd.DataFrame(return_data, columns=fields) 224 225 def list_by_chunks(self, model_class: str, filter_dict: dict = None, 226 exclude_dict: dict = None, auth_header: dict = None, 227 fields: list = None, default_fields: bool = False, 228 chunk_size: int = 50000, limit: int = None, 229 base_filter_skip: list = None, 230 as_dataframe: bool = False, **kwargs 231 ) -> Union[List[dict], pd.DataFrame]: 232 """List objects by fetching them in chunks using PK to paginate. 233 234 Fetch data in chunks to handle large datasets without causing backend 235 timeouts or memory issues. Results are ordered by the 'id' column 236 to ensure consistent pagination. Note that custom ordering is not 237 supported in this method. 238 239 Args: 240 model_class (str): 241 Model class of the end-point. 242 filter_dict (dict): 243 Filter dictionary for the query. 244 exclude_dict (dict): 245 Exclude dictionary for the query. 246 auth_header (dict): 247 Authentication header for user impersonation. 248 fields (list): 249 List of fields to be returned. 250 default_fields (bool): 251 If True and fields is None, return default backend fields. 252 chunk_size (int): 253 Number of objects to fetch per query. Defaults to 50000. 254 base_filter_skip (list): 255 List of base query filters to skip (requires superuser). 256 limit (int): 257 Maximum number of records to return. 258 as_dataframe (bool): 259 If True, returns the results as a pandas DataFrame. 260 **kwargs: 261 Additional arguments for compatibility. 262 263 Returns: 264 Union[List[dict], pd.DataFrame]: 265 A list of dictionaries or a pandas DataFrame containing the 266 serialized objects. 267 268 Raises: 269 PumpWoodException: 270 If there is an error during the request or data processing. 271 """ 272 filter_dict = ( 273 {} if filter_dict is None else filter_dict) 274 exclude_dict = ( 275 {} if exclude_dict is None else exclude_dict) 276 277 base_filter_skip = self._resolve_base_filter_skip( 278 base_filter_skip) 279 280 copy_filter_dict = copy.deepcopy(filter_dict) 281 list_all_results = [] 282 max_order_col = None 283 results_count = 0 284 info_msg = ( 285 "# Fetching chunk: results_count[{results_count}] | " 286 "min_id [{max_order_col}] | limit[{limit}]") 287 while True: 288 logger.info( 289 info_msg, results_count=results_count, 290 max_order_col=max_order_col, limit=limit) 291 292 # It is necessary to keep the initial query id__gt in case of 293 # this parameter being passed as argument. 294 if max_order_col is not None: 295 copy_filter_dict["id__gt"] = max_order_col 296 297 temp_results = self.list( 298 model_class=model_class, filter_dict=copy_filter_dict, 299 exclude_dict=exclude_dict, order_by=["id"], 300 auth_header=auth_header, fields=fields, 301 default_fields=default_fields, limit=chunk_size, 302 base_filter_skip=base_filter_skip) 303 results_count = results_count + len(temp_results) 304 305 # Break if results is empty 306 if len(temp_results) == 0: 307 break 308 309 # Extend the list of objects retrieved 310 list_all_results.extend(temp_results) 311 312 # If limit of objects is set, the counter will limit the number 313 # of objects fetched 314 if limit is not None: 315 records_to_fetch = limit - results_count 316 chunk_size = min(records_to_fetch, chunk_size) 317 # Do not request an empty list 318 if chunk_size == 0: 319 break 320 321 # Get the last object id. If pk is a string the pk is a base64 322 # object that will contain the id columns in it. 323 last_pk = temp_results[-1]["pk"] 324 if isinstance(last_pk, str): 325 loaded_pk_dict = CompositePkBase64Converter.load(last_pk) 326 max_order_col = loaded_pk_dict["id"] 327 else: 328 max_order_col = temp_results[-1]["pk"] 329 330 if as_dataframe: 331 # Return the results as a dataframe using the group_by columns 332 # and the columns created at aggregation to ensure that 333 # even empty results would have the correct columns 334 return pd.DataFrame(list_all_results, columns=fields) 335 return list_all_results 336 337 @staticmethod 338 def _build_list_without_pag_url(model_class: str): 339 return "rest/%s/list-without-pag/" % (model_class.lower(),) 340 341 def list_without_pag(self, model_class: str, filter_dict: dict = None, 342 exclude_dict: dict = None, order_by: list = None, 343 auth_header: dict = None, 344 convert_geometry: bool = True, 345 fields: list = None, 346 default_fields: bool = False, 347 foreign_key_fields: bool = False, 348 base_filter_skip: list = None, 349 as_dataframe: bool = False, 350 **kwargs 351 ) -> List[dict]: 352 """List object without pagination. 353 354 Function to post at list end-point (resumed data) of PumpWood like 355 systems, results won't be paginated. 356 **Be carefull with large returns.** 357 358 Args: 359 model_class (str): 360 Model class of the end-point 361 filter_dict (dict): 362 Filter dict to be used at the query. Filter elements from query 363 return that satifies all statements of the dictonary. 364 exclude_dict (dict): 365 Exclude dict to be used at the query. Remove elements from 366 query return that satifies all statements of the dictonary. 367 order_by (bool): 368 Order results acording to list of strings 369 correspondent to fields. It is possible to use '-' at the 370 begginng of the field name for reverse ordering. Ex.: 371 ['description'] for accendent ordering and ['-description'] 372 for descendent ordering. 373 auth_header (dict): 374 Auth header to substitute the microservice original 375 at the request (user impersonation). 376 fields (List[str]): 377 Set the fields to be returned by the list end-point. 378 default_fields (bool): 379 Boolean, if true and fields arguments None will return the 380 default fields set for list by the backend. 381 limit (int): 382 Set the limit of elements of the returned query. By default, 383 backend usually return 50 elements. 384 foreign_key_fields (bool): 385 Return forenging key objects. It will return the fk 386 corresponding object. Ex: `created_by_id` reference to 387 a user `model_class` the correspondent to User will be 388 returned at `created_by`. 389 convert_geometry (bool): 390 If geometry columns should be convert to shapely geometry. 391 Fields with key 'geometry' will be considered geometry. 392 base_filter_skip (list): 393 List of base query filter to be skiped, it is necessary to 394 be superuser to skip base query filters. 395 as_dataframe (bool): 396 Return data as dataframe and set the columns acording to 397 fields if set. 398 **kwargs: 399 Other unused arguments for compatibility. 400 401 Returns: 402 Containing objects serialized by list Serializer. 403 404 Raises: 405 No especific raises. 406 """ 407 filter_dict = {} if filter_dict is None else filter_dict 408 exclude_dict = {} if exclude_dict is None else exclude_dict 409 url_str = self._build_list_without_pag_url(model_class) 410 411 base_filter_skip = self._resolve_base_filter_skip( 412 base_filter_skip) 413 414 post_data = { 415 'filter_dict': filter_dict, 'exclude_dict': exclude_dict, 416 'order_by': order_by, 'default_fields': default_fields, 417 'foreign_key_fields': foreign_key_fields} 418 419 if fields is not None: 420 post_data["fields"] = fields 421 results = self.request_post( 422 url=url_str, data=post_data, 423 parameters={'base_filter_skip': base_filter_skip}, 424 auth_header=auth_header) 425 426 ################################################## 427 # Converting geometry to Shapely objects in Python 428 geometry_in_results = False 429 if convert_geometry: 430 for obj in results: 431 geometry_value = obj.get("geometry") 432 if geometry_value is not None: 433 obj["geometry"] = geometry.shape(geometry_value) 434 geometry_in_results = True 435 ################################################## 436 437 if not as_dataframe: 438 return results 439 else: 440 convert_to_geopandas = ( 441 (model_class.lower() == "descriptiongeoarea") and 442 geometry_in_results) 443 if convert_to_geopandas: 444 return geopd.GeoDataFrame( 445 results, geometry='geometry', columns=fields) 446 else: 447 return pd.DataFrame( 448 results, columns=fields)
Abstract class for parallel calls at Pumpwood end-points.
22 def list(self, model_class: str, filter_dict: dict = None, 23 exclude_dict: dict = None, order_by: list = None, 24 auth_header: dict = None, fields: list = None, 25 default_fields: bool = False, limit: int = None, 26 foreign_key_fields: bool = False, 27 base_filter_skip: list[str] = None, 28 as_dataframe: bool = False, 29 **kwargs) -> List[dict]: 30 """List objects with pagination. 31 32 List end-point (resumed data) of PumpWood like systems, 33 results will be paginated. To get next pag, send all recived pk at 34 exclude dict (ex.: `exclude_dict={pk__in: [1,2,...,30]}`). 35 36 It is possible to return foreign keys objects associated with 37 `model_class`. Use this with carefull since increase the backend 38 infrastructure consumption, each object is a retrieve call per 39 foreign key (otimization in progress). 40 41 It is possible to use diferent operators using `__` after the name 42 of the field, some of the operators avaiable: 43 44 ### General operators 45 - **__eq:** Check if the value is the same, same results if no 46 operator is passed. 47 - **__gt:** Check if value is greter then argument. 48 - **__lt:** Check if value is less then argument. 49 - **__gte:** Check if value is greter or equal then argument. 50 - **__lte:** Check if value is less or equal then argument. 51 - **__in:** Check if value is at a list, the argument of this operator 52 must be a list. 53 54 ### Text field operators 55 - **__contains:** Check if value contains a string. It is case and 56 accent sensitive. 57 - **__icontains:** Check if a values contains a string, It is case 58 insensitive and accent sensitive. 59 - **__unaccent_icontains:** Check if a values contains a string, It is 60 case insensitive and accent insensitive (consider a, à , á, ã, ... 61 the same). 62 - **__exact:** Same as __eq or not setting operator. 63 - **__iexact:** Same as __eq, but case insensitive and 64 accent sensitive. 65 - **__unaccent_iexact:** Same as __eq, but case insensitive and 66 accent insensitive. 67 - **__startswith:** Check if the value stats with a sub-string. 68 Case sensitive and accent sensitive. 69 - **__istartswith:** Check if the value stats with a sub-string. 70 Case insensitive and accent sensitive. 71 - **__unaccent_istartswith:** Check if the value stats with a 72 sub-string. Case insensitive and accent insensitive. 73 - **__endswith:** Check if the value ends with a sub-string. Case 74 sensitive and accent sensitive. 75 - **__iendswith:** Check if the value ends with a sub-string. Case 76 insensitive and accent sensitive. 77 - **__unaccent_iendswith:** Check if the value ends with a sub-string. 78 Case insensitive and accent insensitive. 79 80 ### Null operators 81 - **__isnull:** Check if field is null, it uses as argument a `boolean` 82 value false will return all non NULL values and true will return 83 NULL values. 84 85 ### Date and datetime operators: 86 - **__range:** Receive as argument a list of two elements and return 87 objects that field dates are between those values. 88 - **__year:** Return object that date field value year is equal to 89 argument. 90 - **__month:** Return object that date field value month is equal to 91 argument. 92 - **__day:** Return object that date field value day is equal to 93 argument. 94 95 ### Dictionary fields operators: 96 - **__json_contained_by:** 97 Uses the function [contained_by](https://docs.sqlalchemy.org/en/20/dialects/postgresql.html#sqlalchemy.dialects.postgresql.JSONB.Comparator.contained_by) 98 from SQLAlchemy to test if keys are a proper subset of the keys of 99 the argument jsonb expression (extracted from SQLAlchemy). The 100 argument is a list. 101 - **__json_has_any:** 102 Uses the function [has_any](https://docs.sqlalchemy.org/en/20/dialects/postgresql.html#sqlalchemy.dialects.postgresql.JSONB.Comparator.has_any) 103 from SQLAlchemy to test for presence of a key. Note that the key 104 may be a SQLA expression. (extracted from SQLAlchemy). The 105 argument is a list. 106 - **__json_has_key:** 107 Uses the function [has_key](https://docs.sqlalchemy.org/en/20/dialects/postgresql.html#sqlalchemy.dialects.postgresql.JSONB.Comparator.has_key) 108 from SQLAlchemy to Test for presence of a key. Note that the key 109 may be a SQLA expression. The argument is a str. 110 111 ### Text similarity operators 112 To use similariry querys on Postgres it is necessary to `pg_trgm` be 113 instaled on server. Check [oficial documentation] 114 (https://www.postgresql.org/docs/current/pgtrgm.html). 115 116 - **__similarity:** Check if two strings are similar uses the `%` 117 operador. 118 - **__word_similar_left:** Check if two strings are similar uses the 119 `<%` operador. 120 - **__word_similar_right:** Check if two strings are similar uses the 121 `%>` operador. 122 - **__strict_word__similar_left:** Check if two strings are similar 123 uses the `<<%` operador. 124 - **__strict_word__similar_right:** Check if two strings are similar 125 uses the `%>>` operador. 126 127 Some usage examples: 128 ```python 129 # Return the first 3 results ordered decreasing acording to `time` and 130 # them ordered by `modeling_unit_id`. Results must have time greater 131 # or equal to 2017-01-01 and less or equal to 2017-06-01. It also 132 # must have attribute_id equal to 6 and not contains modeling_unit_id 133 # 3 or 4. 134 microservice.list( 135 model_class="DatabaseVariable", 136 filter_dict={ 137 "time__gte": "2017-01-01 00:00:00", 138 "time__lte": "2017-06-01 00:00:00", 139 "attribute_id": 6}, 140 exclude_dict={ 141 "modeling_unit_id__in": [3, 4]}, 142 order_by=["-time", "modeling_unit_id"], 143 limit=3, 144 fields=["pk", "model_class", "time", "modeling_unit_id", "value"]) 145 146 # Return all elements that dimensions field has a key type with 147 # value contains `selling` insensitive to case and accent. 148 microservice.list( 149 model_class="DatabaseAttribute", 150 filter_dict={ 151 "dimensions->type__unaccent_icontains": "selling"}) 152 ``` 153 154 Args: 155 model_class: 156 Model class of the end-point 157 filter_dict: 158 Filter dict to be used at the query. Filter elements from query 159 return that satifies all statements of the dictonary. 160 exclude_dict: 161 Exclude dict to be used at the query. Remove elements from 162 query return that satifies all statements of the dictonary. 163 order_by: Order results acording to list of strings 164 correspondent to fields. It is possible to use '-' at the 165 begginng of the field name for reverse ordering. Ex.: 166 ['description'] for accendent ordering and ['-description'] 167 for descendent ordering. 168 auth_header: 169 Auth header to substitute the microservice original 170 at the request (user impersonation). 171 fields (list): 172 Set the fields to be returned by the list end-point. 173 default_fields (bool): 174 Boolean, if true and fields arguments None will return the 175 default fields set for list by the backend. 176 limit (int): 177 Set the limit of elements of the returned query. By default, 178 backend usually return 50 elements. 179 foreign_key_fields (bool): 180 Return forenging key objects. It will return the fk 181 corresponding object. Ex: `created_by_id` reference to 182 a user `model_class` the correspondent to User will be 183 returned at `created_by`. 184 base_filter_skip (list): 185 List of base query filter to be skiped, it is necessary to 186 be superuser to skip base query filters. 187 as_dataframe (bool): 188 Return data as dataframe and set the columns acording to 189 fields if set. 190 **kwargs: 191 Other parameters for compatibility. 192 193 Returns: 194 Containing objects serialized by list Serializer. 195 196 Raises: 197 No especific raises. 198 """ # NOQA 199 filter_dict = {} if filter_dict is None else filter_dict 200 exclude_dict = {} if exclude_dict is None else exclude_dict 201 order_by = [] if order_by is None else order_by 202 203 base_filter_skip = self._resolve_base_filter_skip( 204 base_filter_skip) 205 206 url_str = self._build_list_url(model_class) 207 post_data = { 208 'filter_dict': filter_dict, 'exclude_dict': exclude_dict, 209 'order_by': order_by, 'default_fields': default_fields, 210 'limit': limit, 'foreign_key_fields': foreign_key_fields} 211 if fields is not None: 212 post_data["fields"] = fields 213 return_data = self.request_post( 214 url=url_str, data=post_data, 215 parameters={'base_filter_skip': base_filter_skip}, 216 auth_header=auth_header) 217 if not as_dataframe: 218 return return_data 219 else: 220 # Return the results as a dataframe using the group_by columns 221 # and the columns created at aggregation to ensure that 222 # even empty results would have the correct columns 223 return pd.DataFrame(return_data, columns=fields)
List objects with pagination.
List end-point (resumed data) of PumpWood like systems,
results will be paginated. To get next pag, send all recived pk at
exclude dict (ex.: exclude_dict={pk__in: [1,2,...,30]}).
It is possible to return foreign keys objects associated with
model_class. Use this with carefull since increase the backend
infrastructure consumption, each object is a retrieve call per
foreign key (otimization in progress).
It is possible to use diferent operators using __ after the name
of the field, some of the operators avaiable:
General operators
- __eq: Check if the value is the same, same results if no operator is passed.
- __gt: Check if value is greter then argument.
- __lt: Check if value is less then argument.
- __gte: Check if value is greter or equal then argument.
- __lte: Check if value is less or equal then argument.
- __in: Check if value is at a list, the argument of this operator must be a list.
Text field operators
- __contains: Check if value contains a string. It is case and accent sensitive.
- __icontains: Check if a values contains a string, It is case insensitive and accent sensitive.
- __unaccent_icontains: Check if a values contains a string, It is case insensitive and accent insensitive (consider a, à , á, ã, ... the same).
- __exact: Same as __eq or not setting operator.
- __iexact: Same as __eq, but case insensitive and accent sensitive.
- __unaccent_iexact: Same as __eq, but case insensitive and accent insensitive.
- __startswith: Check if the value stats with a sub-string. Case sensitive and accent sensitive.
- __istartswith: Check if the value stats with a sub-string. Case insensitive and accent sensitive.
- __unaccent_istartswith: Check if the value stats with a sub-string. Case insensitive and accent insensitive.
- __endswith: Check if the value ends with a sub-string. Case sensitive and accent sensitive.
- __iendswith: Check if the value ends with a sub-string. Case insensitive and accent sensitive.
- __unaccent_iendswith: Check if the value ends with a sub-string. Case insensitive and accent insensitive.
Null operators
- __isnull: Check if field is null, it uses as argument a
booleanvalue false will return all non NULL values and true will return NULL values.
Date and datetime operators:
- __range: Receive as argument a list of two elements and return objects that field dates are between those values.
- __year: Return object that date field value year is equal to argument.
- __month: Return object that date field value month is equal to argument.
- __day: Return object that date field value day is equal to argument.
Dictionary fields operators:
- __json_contained_by: Uses the function contained_by from SQLAlchemy to test if keys are a proper subset of the keys of the argument jsonb expression (extracted from SQLAlchemy). The argument is a list.
- __json_has_any: Uses the function has_any from SQLAlchemy to test for presence of a key. Note that the key may be a SQLA expression. (extracted from SQLAlchemy). The argument is a list.
- __json_has_key: Uses the function has_key from SQLAlchemy to Test for presence of a key. Note that the key may be a SQLA expression. The argument is a str.
Text similarity operators
To use similariry querys on Postgres it is necessary to pg_trgm be
instaled on server. Check [oficial documentation]
(https://www.postgresql.org/docs/current/pgtrgm.html).
- __similarity: Check if two strings are similar uses the
%operador. - __word_similar_left: Check if two strings are similar uses the
<%operador. - __word_similar_right: Check if two strings are similar uses the
%>operador. - __strict_word__similar_left: Check if two strings are similar
uses the
<<%operador. - __strict_word__similar_right: Check if two strings are similar
uses the
%>>operador.
Some usage examples:
# Return the first 3 results ordered decreasing acording to `time` and
# them ordered by `modeling_unit_id`. Results must have time greater
# or equal to 2017-01-01 and less or equal to 2017-06-01. It also
# must have attribute_id equal to 6 and not contains modeling_unit_id
# 3 or 4.
microservice.list(
model_class="DatabaseVariable",
filter_dict={
"time__gte": "2017-01-01 00:00:00",
"time__lte": "2017-06-01 00:00:00",
"attribute_id": 6},
exclude_dict={
"modeling_unit_id__in": [3, 4]},
order_by=["-time", "modeling_unit_id"],
limit=3,
fields=["pk", "model_class", "time", "modeling_unit_id", "value"])
# Return all elements that dimensions field has a key type with
# value contains `selling` insensitive to case and accent.
microservice.list(
model_class="DatabaseAttribute",
filter_dict={
"dimensions->type__unaccent_icontains": "selling"})
Arguments:
- model_class: Model class of the end-point
- filter_dict: Filter dict to be used at the query. Filter elements from query return that satifies all statements of the dictonary.
- exclude_dict: Exclude dict to be used at the query. Remove elements from query return that satifies all statements of the dictonary.
- order_by: Order results acording to list of strings correspondent to fields. It is possible to use '-' at the begginng of the field name for reverse ordering. Ex.: ['description'] for accendent ordering and ['-description'] for descendent ordering.
- auth_header: Auth header to substitute the microservice original at the request (user impersonation).
- fields (list): Set the fields to be returned by the list end-point.
- default_fields (bool): Boolean, if true and fields arguments None will return the default fields set for list by the backend.
- limit (int): Set the limit of elements of the returned query. By default, backend usually return 50 elements.
- foreign_key_fields (bool): Return forenging key objects. It will return the fk
corresponding object. Ex:
created_by_idreference to a usermodel_classthe correspondent to User will be returned atcreated_by. - base_filter_skip (list): List of base query filter to be skiped, it is necessary to be superuser to skip base query filters.
- as_dataframe (bool): Return data as dataframe and set the columns acording to fields if set.
- **kwargs: Other parameters for compatibility.
Returns:
Containing objects serialized by list Serializer.
Raises:
- No especific raises.
225 def list_by_chunks(self, model_class: str, filter_dict: dict = None, 226 exclude_dict: dict = None, auth_header: dict = None, 227 fields: list = None, default_fields: bool = False, 228 chunk_size: int = 50000, limit: int = None, 229 base_filter_skip: list = None, 230 as_dataframe: bool = False, **kwargs 231 ) -> Union[List[dict], pd.DataFrame]: 232 """List objects by fetching them in chunks using PK to paginate. 233 234 Fetch data in chunks to handle large datasets without causing backend 235 timeouts or memory issues. Results are ordered by the 'id' column 236 to ensure consistent pagination. Note that custom ordering is not 237 supported in this method. 238 239 Args: 240 model_class (str): 241 Model class of the end-point. 242 filter_dict (dict): 243 Filter dictionary for the query. 244 exclude_dict (dict): 245 Exclude dictionary for the query. 246 auth_header (dict): 247 Authentication header for user impersonation. 248 fields (list): 249 List of fields to be returned. 250 default_fields (bool): 251 If True and fields is None, return default backend fields. 252 chunk_size (int): 253 Number of objects to fetch per query. Defaults to 50000. 254 base_filter_skip (list): 255 List of base query filters to skip (requires superuser). 256 limit (int): 257 Maximum number of records to return. 258 as_dataframe (bool): 259 If True, returns the results as a pandas DataFrame. 260 **kwargs: 261 Additional arguments for compatibility. 262 263 Returns: 264 Union[List[dict], pd.DataFrame]: 265 A list of dictionaries or a pandas DataFrame containing the 266 serialized objects. 267 268 Raises: 269 PumpWoodException: 270 If there is an error during the request or data processing. 271 """ 272 filter_dict = ( 273 {} if filter_dict is None else filter_dict) 274 exclude_dict = ( 275 {} if exclude_dict is None else exclude_dict) 276 277 base_filter_skip = self._resolve_base_filter_skip( 278 base_filter_skip) 279 280 copy_filter_dict = copy.deepcopy(filter_dict) 281 list_all_results = [] 282 max_order_col = None 283 results_count = 0 284 info_msg = ( 285 "# Fetching chunk: results_count[{results_count}] | " 286 "min_id [{max_order_col}] | limit[{limit}]") 287 while True: 288 logger.info( 289 info_msg, results_count=results_count, 290 max_order_col=max_order_col, limit=limit) 291 292 # It is necessary to keep the initial query id__gt in case of 293 # this parameter being passed as argument. 294 if max_order_col is not None: 295 copy_filter_dict["id__gt"] = max_order_col 296 297 temp_results = self.list( 298 model_class=model_class, filter_dict=copy_filter_dict, 299 exclude_dict=exclude_dict, order_by=["id"], 300 auth_header=auth_header, fields=fields, 301 default_fields=default_fields, limit=chunk_size, 302 base_filter_skip=base_filter_skip) 303 results_count = results_count + len(temp_results) 304 305 # Break if results is empty 306 if len(temp_results) == 0: 307 break 308 309 # Extend the list of objects retrieved 310 list_all_results.extend(temp_results) 311 312 # If limit of objects is set, the counter will limit the number 313 # of objects fetched 314 if limit is not None: 315 records_to_fetch = limit - results_count 316 chunk_size = min(records_to_fetch, chunk_size) 317 # Do not request an empty list 318 if chunk_size == 0: 319 break 320 321 # Get the last object id. If pk is a string the pk is a base64 322 # object that will contain the id columns in it. 323 last_pk = temp_results[-1]["pk"] 324 if isinstance(last_pk, str): 325 loaded_pk_dict = CompositePkBase64Converter.load(last_pk) 326 max_order_col = loaded_pk_dict["id"] 327 else: 328 max_order_col = temp_results[-1]["pk"] 329 330 if as_dataframe: 331 # Return the results as a dataframe using the group_by columns 332 # and the columns created at aggregation to ensure that 333 # even empty results would have the correct columns 334 return pd.DataFrame(list_all_results, columns=fields) 335 return list_all_results
List objects by fetching them in chunks using PK to paginate.
Fetch data in chunks to handle large datasets without causing backend timeouts or memory issues. Results are ordered by the 'id' column to ensure consistent pagination. Note that custom ordering is not supported in this method.
Arguments:
- model_class (str): Model class of the end-point.
- filter_dict (dict): Filter dictionary for the query.
- exclude_dict (dict): Exclude dictionary for the query.
- auth_header (dict): Authentication header for user impersonation.
- fields (list): List of fields to be returned.
- default_fields (bool): If True and fields is None, return default backend fields.
- chunk_size (int): Number of objects to fetch per query. Defaults to 50000.
- base_filter_skip (list): List of base query filters to skip (requires superuser).
- limit (int): Maximum number of records to return.
- as_dataframe (bool): If True, returns the results as a pandas DataFrame.
- **kwargs: Additional arguments for compatibility.
Returns:
Union[List[dict], pd.DataFrame]: A list of dictionaries or a pandas DataFrame containing the serialized objects.
Raises:
- PumpWoodException: If there is an error during the request or data processing.
341 def list_without_pag(self, model_class: str, filter_dict: dict = None, 342 exclude_dict: dict = None, order_by: list = None, 343 auth_header: dict = None, 344 convert_geometry: bool = True, 345 fields: list = None, 346 default_fields: bool = False, 347 foreign_key_fields: bool = False, 348 base_filter_skip: list = None, 349 as_dataframe: bool = False, 350 **kwargs 351 ) -> List[dict]: 352 """List object without pagination. 353 354 Function to post at list end-point (resumed data) of PumpWood like 355 systems, results won't be paginated. 356 **Be carefull with large returns.** 357 358 Args: 359 model_class (str): 360 Model class of the end-point 361 filter_dict (dict): 362 Filter dict to be used at the query. Filter elements from query 363 return that satifies all statements of the dictonary. 364 exclude_dict (dict): 365 Exclude dict to be used at the query. Remove elements from 366 query return that satifies all statements of the dictonary. 367 order_by (bool): 368 Order results acording to list of strings 369 correspondent to fields. It is possible to use '-' at the 370 begginng of the field name for reverse ordering. Ex.: 371 ['description'] for accendent ordering and ['-description'] 372 for descendent ordering. 373 auth_header (dict): 374 Auth header to substitute the microservice original 375 at the request (user impersonation). 376 fields (List[str]): 377 Set the fields to be returned by the list end-point. 378 default_fields (bool): 379 Boolean, if true and fields arguments None will return the 380 default fields set for list by the backend. 381 limit (int): 382 Set the limit of elements of the returned query. By default, 383 backend usually return 50 elements. 384 foreign_key_fields (bool): 385 Return forenging key objects. It will return the fk 386 corresponding object. Ex: `created_by_id` reference to 387 a user `model_class` the correspondent to User will be 388 returned at `created_by`. 389 convert_geometry (bool): 390 If geometry columns should be convert to shapely geometry. 391 Fields with key 'geometry' will be considered geometry. 392 base_filter_skip (list): 393 List of base query filter to be skiped, it is necessary to 394 be superuser to skip base query filters. 395 as_dataframe (bool): 396 Return data as dataframe and set the columns acording to 397 fields if set. 398 **kwargs: 399 Other unused arguments for compatibility. 400 401 Returns: 402 Containing objects serialized by list Serializer. 403 404 Raises: 405 No especific raises. 406 """ 407 filter_dict = {} if filter_dict is None else filter_dict 408 exclude_dict = {} if exclude_dict is None else exclude_dict 409 url_str = self._build_list_without_pag_url(model_class) 410 411 base_filter_skip = self._resolve_base_filter_skip( 412 base_filter_skip) 413 414 post_data = { 415 'filter_dict': filter_dict, 'exclude_dict': exclude_dict, 416 'order_by': order_by, 'default_fields': default_fields, 417 'foreign_key_fields': foreign_key_fields} 418 419 if fields is not None: 420 post_data["fields"] = fields 421 results = self.request_post( 422 url=url_str, data=post_data, 423 parameters={'base_filter_skip': base_filter_skip}, 424 auth_header=auth_header) 425 426 ################################################## 427 # Converting geometry to Shapely objects in Python 428 geometry_in_results = False 429 if convert_geometry: 430 for obj in results: 431 geometry_value = obj.get("geometry") 432 if geometry_value is not None: 433 obj["geometry"] = geometry.shape(geometry_value) 434 geometry_in_results = True 435 ################################################## 436 437 if not as_dataframe: 438 return results 439 else: 440 convert_to_geopandas = ( 441 (model_class.lower() == "descriptiongeoarea") and 442 geometry_in_results) 443 if convert_to_geopandas: 444 return geopd.GeoDataFrame( 445 results, geometry='geometry', columns=fields) 446 else: 447 return pd.DataFrame( 448 results, columns=fields)
List object without pagination.
Function to post at list end-point (resumed data) of PumpWood like systems, results won't be paginated. Be carefull with large returns.
Arguments:
- model_class (str): Model class of the end-point
- filter_dict (dict): Filter dict to be used at the query. Filter elements from query return that satifies all statements of the dictonary.
- exclude_dict (dict): Exclude dict to be used at the query. Remove elements from query return that satifies all statements of the dictonary.
- order_by (bool): Order results acording to list of strings correspondent to fields. It is possible to use '-' at the begginng of the field name for reverse ordering. Ex.: ['description'] for accendent ordering and ['-description'] for descendent ordering.
- auth_header (dict): Auth header to substitute the microservice original at the request (user impersonation).
- fields (List[str]): Set the fields to be returned by the list end-point.
- default_fields (bool): Boolean, if true and fields arguments None will return the default fields set for list by the backend.
- limit (int): Set the limit of elements of the returned query. By default, backend usually return 50 elements.
- foreign_key_fields (bool): Return forenging key objects. It will return the fk
corresponding object. Ex:
created_by_idreference to a usermodel_classthe correspondent to User will be returned atcreated_by. - convert_geometry (bool): If geometry columns should be convert to shapely geometry. Fields with key 'geometry' will be considered geometry.
- base_filter_skip (list): List of base query filter to be skiped, it is necessary to be superuser to skip base query filters.
- as_dataframe (bool): Return data as dataframe and set the columns acording to fields if set.
- **kwargs: Other unused arguments for compatibility.
Returns:
Containing objects serialized by list Serializer.
Raises:
- No especific raises.
8class ABCSimpleDimensionMicroservice(ABC, PumpWoodMicroServiceBase): 9 """Abstract class for parallel calls at Pumpwood end-points.""" 10 11 @staticmethod 12 def _build_list_dimensions(model_class: str): 13 return "rest/%s/list-dimensions/" % (model_class.lower(),) 14 15 def list_dimensions(self, model_class: str, filter_dict: dict = {}, 16 exclude_dict: dict = {}, auth_header: dict = None, 17 base_filter_skip: list = None) -> list[str]: 18 """List dimensions avaiable for model_class. 19 20 It list all keys avaiable at dimension retricting the results with 21 query parameters `filter_dict` and `exclude_dict`. 22 23 Args: 24 model_class: 25 Model class of the end-point 26 filter_dict: 27 Filter dict to be used at the query. Filter elements from query 28 return that satifies all statements of the dictonary. 29 exclude_dict: 30 Exclude dict to be used at the query. Remove elements from 31 query return that satifies all statements of the dictonary. 32 auth_header: 33 Auth header to substitute the microservice original 34 at the request (user impersonation). 35 base_filter_skip (list): 36 List of base query filter to be skiped, it is necessary to 37 be superuser to skip base query filters. 38 39 Returns: 40 List of keys avaiable in results from the query dict. 41 """ 42 url_str = self._build_list_dimensions(model_class) 43 base_filter_skip = self._resolve_base_filter_skip( 44 base_filter_skip) 45 46 post_data = {'filter_dict': filter_dict, 'exclude_dict': exclude_dict} 47 return self.request_post( 48 url=url_str, data=post_data, 49 parameters={'base_filter_skip': base_filter_skip}, 50 auth_header=auth_header) 51 52 @staticmethod 53 def _build_list_dimension_values(model_class: str): 54 return "rest/%s/list-dimension-values/" % (model_class.lower(), ) 55 56 def list_dimension_values(self, model_class: str, key: str, 57 filter_dict: dict = {}, exclude_dict: dict = {}, 58 auth_header: dict = None, 59 base_filter_skip: list = None) -> list[any]: 60 """List values associated with dimensions key. 61 62 It list all keys avaiable at dimension retricting the results with 63 query parameters `filter_dict` and `exclude_dict`. 64 65 Args: 66 model_class: 67 Model class of the end-point 68 filter_dict: 69 Filter dict to be used at the query. Filter elements from query 70 return that satifies all statements of the dictonary. 71 exclude_dict: 72 Exclude dict to be used at the query. Remove elements from 73 query return that satifies all statements of the dictonary. 74 auth_header: 75 Auth header to substitute the microservice original 76 at the request (user impersonation). 77 key: 78 Key to list the avaiable values using the query filter 79 and exclude. 80 base_filter_skip (list): 81 List of base query filter to be skiped, it is necessary to 82 be superuser to skip base query filters. 83 84 Returns: 85 List of values associated with dimensions key at the objects that 86 are returned with `filter_dict` and `exclude_dict`. 87 """ 88 url_str = self._build_list_dimension_values(model_class) 89 base_filter_skip = self._resolve_base_filter_skip( 90 base_filter_skip) 91 92 post_data = { 93 'filter_dict': filter_dict, 'exclude_dict': exclude_dict, 94 'key': key} 95 return self.request_post( 96 url=url_str, data=post_data, 97 parameters={'base_filter_skip': base_filter_skip}, 98 auth_header=auth_header)
Abstract class for parallel calls at Pumpwood end-points.
15 def list_dimensions(self, model_class: str, filter_dict: dict = {}, 16 exclude_dict: dict = {}, auth_header: dict = None, 17 base_filter_skip: list = None) -> list[str]: 18 """List dimensions avaiable for model_class. 19 20 It list all keys avaiable at dimension retricting the results with 21 query parameters `filter_dict` and `exclude_dict`. 22 23 Args: 24 model_class: 25 Model class of the end-point 26 filter_dict: 27 Filter dict to be used at the query. Filter elements from query 28 return that satifies all statements of the dictonary. 29 exclude_dict: 30 Exclude dict to be used at the query. Remove elements from 31 query return that satifies all statements of the dictonary. 32 auth_header: 33 Auth header to substitute the microservice original 34 at the request (user impersonation). 35 base_filter_skip (list): 36 List of base query filter to be skiped, it is necessary to 37 be superuser to skip base query filters. 38 39 Returns: 40 List of keys avaiable in results from the query dict. 41 """ 42 url_str = self._build_list_dimensions(model_class) 43 base_filter_skip = self._resolve_base_filter_skip( 44 base_filter_skip) 45 46 post_data = {'filter_dict': filter_dict, 'exclude_dict': exclude_dict} 47 return self.request_post( 48 url=url_str, data=post_data, 49 parameters={'base_filter_skip': base_filter_skip}, 50 auth_header=auth_header)
List dimensions avaiable for model_class.
It list all keys avaiable at dimension retricting the results with
query parameters filter_dict and exclude_dict.
Arguments:
- model_class: Model class of the end-point
- filter_dict: Filter dict to be used at the query. Filter elements from query return that satifies all statements of the dictonary.
- exclude_dict: Exclude dict to be used at the query. Remove elements from query return that satifies all statements of the dictonary.
- auth_header: Auth header to substitute the microservice original at the request (user impersonation).
- base_filter_skip (list): List of base query filter to be skiped, it is necessary to be superuser to skip base query filters.
Returns:
List of keys avaiable in results from the query dict.
56 def list_dimension_values(self, model_class: str, key: str, 57 filter_dict: dict = {}, exclude_dict: dict = {}, 58 auth_header: dict = None, 59 base_filter_skip: list = None) -> list[any]: 60 """List values associated with dimensions key. 61 62 It list all keys avaiable at dimension retricting the results with 63 query parameters `filter_dict` and `exclude_dict`. 64 65 Args: 66 model_class: 67 Model class of the end-point 68 filter_dict: 69 Filter dict to be used at the query. Filter elements from query 70 return that satifies all statements of the dictonary. 71 exclude_dict: 72 Exclude dict to be used at the query. Remove elements from 73 query return that satifies all statements of the dictonary. 74 auth_header: 75 Auth header to substitute the microservice original 76 at the request (user impersonation). 77 key: 78 Key to list the avaiable values using the query filter 79 and exclude. 80 base_filter_skip (list): 81 List of base query filter to be skiped, it is necessary to 82 be superuser to skip base query filters. 83 84 Returns: 85 List of values associated with dimensions key at the objects that 86 are returned with `filter_dict` and `exclude_dict`. 87 """ 88 url_str = self._build_list_dimension_values(model_class) 89 base_filter_skip = self._resolve_base_filter_skip( 90 base_filter_skip) 91 92 post_data = { 93 'filter_dict': filter_dict, 'exclude_dict': exclude_dict, 94 'key': key} 95 return self.request_post( 96 url=url_str, data=post_data, 97 parameters={'base_filter_skip': base_filter_skip}, 98 auth_header=auth_header)
List values associated with dimensions key.
It list all keys avaiable at dimension retricting the results with
query parameters filter_dict and exclude_dict.
Arguments:
- model_class: Model class of the end-point
- filter_dict: Filter dict to be used at the query. Filter elements from query return that satifies all statements of the dictonary.
- exclude_dict: Exclude dict to be used at the query. Remove elements from query return that satifies all statements of the dictonary.
- auth_header: Auth header to substitute the microservice original at the request (user impersonation).
- key: Key to list the avaiable values using the query filter and exclude.
- base_filter_skip (list): List of base query filter to be skiped, it is necessary to be superuser to skip base query filters.
Returns:
List of values associated with dimensions key at the objects that are returned with
filter_dictandexclude_dict.
8class ABCSimpleActionMicroservice(ABC, PumpWoodMicroServiceBase): 9 """Abstract class for parallel calls at Pumpwood end-points.""" 10 11 def list_actions(self, model_class: str, 12 auth_header: dict = None) -> list[dict]: 13 """Return a list of all actions avaiable at this model class. 14 15 Args: 16 model_class: 17 Model class to list possible actions. 18 auth_header: 19 Auth header to substitute the microservice original 20 at the request (user impersonation). 21 22 Returns: 23 List of possible actions and its descriptions. 24 25 Raises: 26 No particular errors. 27 """ 28 url_str = "rest/%s/actions/" % (model_class.lower()) 29 return self.request_get(url=url_str, auth_header=auth_header) 30 31 @staticmethod 32 def _build_execute_action_url(model_class: str, action: str, 33 pk: int = None): 34 url_str = "rest/%s/actions/%s/" % (model_class.lower(), action) 35 if pk is not None: 36 url_str = url_str + str(pk) + '/' 37 return url_str 38 39 def execute_action(self, model_class: str, action: str, pk: int = None, 40 parameters: dict = {}, files: list = None, 41 auth_header: dict = None, 42 base_filter_skip: list[str] | list[list[str]] = None 43 ) -> dict: 44 """Execute action associated with a model class. 45 46 If action is static or classfunction no pk is necessary. 47 48 Args: 49 pk (int): 50 PK of the object to run action at. If not set action will be 51 considered a classmethod and will run over the class. 52 model_class: 53 Model class to run action the object 54 action: 55 Action that will be performed. 56 auth_header: 57 Auth header to substitute the microservice original 58 at the request (user impersonation). 59 parameters: 60 Dictionary with the function parameters. 61 files: 62 A dictionary of files to be added to as a multi-part 63 post request. File must be passed as a file object with read 64 bytes. 65 base_filter_skip (list): 66 List of base query filter to be skiped, it is necessary to 67 be superuser to skip base query filters. 68 69 Returns: 70 Return a dictonary with keys: 71 - **result:**: Result of the action that was performed. 72 - **action:**: Information of the action that was performed. 73 - **parameters:** Parameters that were passed to perform the 74 action. 75 - **object:** If a pk was passed to execute and action (not 76 classmethod or staticmethod), the object with the correspondent 77 pk is returned. 78 79 Raises: 80 PumpWoodException: 81 'There is no method {action} in rest actions for {class_name}'. 82 This indicates that action requested is not associated with 83 the model_class. 84 PumpWoodActionArgsException: 85 'Function is not static and pk is Null'. This indicate that 86 the action solicitated is not static/class method and a pk 87 was not passed as argument. 88 PumpWoodActionArgsException: 89 'Function is static and pk is not Null'. This indicate that 90 the action solicitated is static/class method and a pk 91 was passed as argument. 92 PumpWoodObjectDoesNotExist: 93 'Requested object {model_class}[{pk}] not found.'. This 94 indicate that pk associated with model class was not found 95 on database. 96 """ 97 base_filter_skip = self._resolve_base_filter_skip( 98 base_filter_skip) 99 100 url_str = self._build_execute_action_url( 101 model_class=model_class, action=action, pk=pk) 102 return self.request_post( 103 url=url_str, data=parameters, files=files, auth_header=auth_header, 104 parameters={"base_filter_skip": base_filter_skip})
Abstract class for parallel calls at Pumpwood end-points.
11 def list_actions(self, model_class: str, 12 auth_header: dict = None) -> list[dict]: 13 """Return a list of all actions avaiable at this model class. 14 15 Args: 16 model_class: 17 Model class to list possible actions. 18 auth_header: 19 Auth header to substitute the microservice original 20 at the request (user impersonation). 21 22 Returns: 23 List of possible actions and its descriptions. 24 25 Raises: 26 No particular errors. 27 """ 28 url_str = "rest/%s/actions/" % (model_class.lower()) 29 return self.request_get(url=url_str, auth_header=auth_header)
Return a list of all actions avaiable at this model class.
Arguments:
- model_class: Model class to list possible actions.
- auth_header: Auth header to substitute the microservice original at the request (user impersonation).
Returns:
List of possible actions and its descriptions.
Raises:
- No particular errors.
39 def execute_action(self, model_class: str, action: str, pk: int = None, 40 parameters: dict = {}, files: list = None, 41 auth_header: dict = None, 42 base_filter_skip: list[str] | list[list[str]] = None 43 ) -> dict: 44 """Execute action associated with a model class. 45 46 If action is static or classfunction no pk is necessary. 47 48 Args: 49 pk (int): 50 PK of the object to run action at. If not set action will be 51 considered a classmethod and will run over the class. 52 model_class: 53 Model class to run action the object 54 action: 55 Action that will be performed. 56 auth_header: 57 Auth header to substitute the microservice original 58 at the request (user impersonation). 59 parameters: 60 Dictionary with the function parameters. 61 files: 62 A dictionary of files to be added to as a multi-part 63 post request. File must be passed as a file object with read 64 bytes. 65 base_filter_skip (list): 66 List of base query filter to be skiped, it is necessary to 67 be superuser to skip base query filters. 68 69 Returns: 70 Return a dictonary with keys: 71 - **result:**: Result of the action that was performed. 72 - **action:**: Information of the action that was performed. 73 - **parameters:** Parameters that were passed to perform the 74 action. 75 - **object:** If a pk was passed to execute and action (not 76 classmethod or staticmethod), the object with the correspondent 77 pk is returned. 78 79 Raises: 80 PumpWoodException: 81 'There is no method {action} in rest actions for {class_name}'. 82 This indicates that action requested is not associated with 83 the model_class. 84 PumpWoodActionArgsException: 85 'Function is not static and pk is Null'. This indicate that 86 the action solicitated is not static/class method and a pk 87 was not passed as argument. 88 PumpWoodActionArgsException: 89 'Function is static and pk is not Null'. This indicate that 90 the action solicitated is static/class method and a pk 91 was passed as argument. 92 PumpWoodObjectDoesNotExist: 93 'Requested object {model_class}[{pk}] not found.'. This 94 indicate that pk associated with model class was not found 95 on database. 96 """ 97 base_filter_skip = self._resolve_base_filter_skip( 98 base_filter_skip) 99 100 url_str = self._build_execute_action_url( 101 model_class=model_class, action=action, pk=pk) 102 return self.request_post( 103 url=url_str, data=parameters, files=files, auth_header=auth_header, 104 parameters={"base_filter_skip": base_filter_skip})
Execute action associated with a model class.
If action is static or classfunction no pk is necessary.
Arguments:
- pk (int): PK of the object to run action at. If not set action will be considered a classmethod and will run over the class.
- model_class: Model class to run action the object
- action: Action that will be performed.
- auth_header: Auth header to substitute the microservice original at the request (user impersonation).
- parameters: Dictionary with the function parameters.
- files: A dictionary of files to be added to as a multi-part post request. File must be passed as a file object with read bytes.
- base_filter_skip (list): List of base query filter to be skiped, it is necessary to be superuser to skip base query filters.
Returns:
Return a dictonary with keys:
- result:: Result of the action that was performed.
- action:: Information of the action that was performed.
- parameters: Parameters that were passed to perform the action.
- object: If a pk was passed to execute and action (not classmethod or staticmethod), the object with the correspondent pk is returned.
Raises:
- PumpWoodException: 'There is no method {action} in rest actions for {class_name}'. This indicates that action requested is not associated with the model_class.
- PumpWoodActionArgsException: 'Function is not static and pk is Null'. This indicate that the action solicitated is not static/class method and a pk was not passed as argument.
- PumpWoodActionArgsException: 'Function is static and pk is not Null'. This indicate that the action solicitated is static/class method and a pk was passed as argument.
- PumpWoodObjectDoesNotExist: 'Requested object {model_class}[{pk}] not found.'. This indicate that pk associated with model class was not found on database.
23class ABCSimpleInfoMicroservice(ABC, PumpWoodMicroServiceBase): 24 """Class for infomation calls.""" 25 26 def search_options(self, model_class: str, 27 auth_header: dict = None) -> dict: 28 """Return search options. 29 30 DEPRECATED Use `list_options` function instead. 31 32 Return information of the fields including available options for 33 options fields and model associated with the foreign key. 34 35 Args: 36 model_class (str): 37 Model class to check search parameters. 38 auth_header (dict, optional): 39 Auth header to substitute the microservice original 40 at the request (user impersonation). 41 42 Returns: 43 Return a dictionary with field names as keys and information of 44 them as values. Information at values: 45 - **primary_key [bool]:**: Boolean indicating if field is part 46 of model_class primary key. 47 - **column [str]:**: Name of the column. 48 - **column__verbose [str]:** Name of the column translated using 49 Pumpwood I8s. 50 - **help_text [str]:** Help text associated with column. 51 - **help_text__verbose [str]:** Help text associated with column 52 translated using Pumpwood I8s. 53 - **type [str]:** Python type associated with the column. 54 - **nullable [bool]:** If field can be set as null (None). 55 - **read_only [bool]:** If field is marked as read-only. Passsing 56 information for this field will not be used in save end-point. 57 - **default [any]:** Default value of the field if not set using 58 save end-poin. 59 - **unique [bool]:** If the there is a constrain in database 60 setting this field to be unique. 61 - **extra_info:** Some extra informations used to pass associated 62 model class for foreign key and related fields. 63 - **in [dict]:** At options fields, have their options listed in 64 `in` keys. It will return the values as key and de description 65 and description__verbose (translated by Pumpwood I8s) 66 as values. 67 - **partition:** At pk field, this key indicates if the database 68 if partitioned. Partitioned will perform better in queries if 69 partition is used on filter or exclude clauses. If table has 70 more than one level o partition, at least the first one must 71 be used when retrieving data. 72 73 Raises: 74 No particular raises. 75 """ 76 url_str = "rest/%s/options/" % (model_class.lower(), ) 77 return self.request_get(url=url_str, auth_header=auth_header) 78 79 def fill_options(self, model_class, parcial_obj_dict: dict = None, 80 field: str = None, auth_header: dict = None, 81 use_disk_cache: bool = True): 82 """Return options for object fields. 83 84 DEPRECATED Use `fill_validation` function instead. 85 86 This function send partial object data and return options to finish 87 object filling. 88 89 Args: 90 model_class (str): 91 Model class to check search parameters. 92 auth_header (dict, optional): 93 Auth header to substitute the microservice original 94 at the request (user impersonation). 95 parcial_obj_dict (dict, optional): 96 Partial object that is sent to backend for validation and 97 update fill options according to values passed for each field. 98 field (str, optional): 99 Restrict validation for a specific field if implemented. 100 use_disk_cache (bool): 101 If it will use the disk cache to retrieve information 102 from fill options if it was not passed partial object 103 data (`parcial_obj_dict=None`) or field (`field=None`). 104 105 Returns: 106 Return a dictionary with field names as keys and information of 107 them as values. Information at values: 108 - **primary_key [bool]:**: Boolean indicating if field is part 109 of model_class primary key. 110 - **column [str]:**: Name of the column. 111 - **column__verbose [str]:** Name of the column translated using 112 Pumpwood I8s. 113 - **help_text [str]:** Help text associated with column. 114 - **help_text__verbose [str]:** Help text associated with column 115 translated using Pumpwood I8s. 116 - **type [str]:** Python type associated with the column. 117 - **nullable [bool]:** If field can be set as null (None). 118 - **read_only [bool]:** If field is marked as read-only. Passsing 119 information for this field will not be used in save end-point. 120 - **default [any]:** Default value of the field if not set using 121 save end-poin. 122 - **unique [bool]:** If the there is a constrain in database 123 setting this field to be unique. 124 - **extra_info:** Some extra informations used to pass associated 125 model class for foreign key and related fields. 126 - **in [dict]:** At options fields, have their options listed in 127 `in` keys. It will return the values as key and de description 128 and description__verbose (translated by Pumpwood I8s) 129 as values. 130 - **partition:** At pk field, this key indicates if the database 131 if partitioned. Partitioned will perform better in queries if 132 partition is used on filter or exclude clauses. If table has 133 more than one level o partition, at least the first one must 134 be used when retrieving data. 135 136 Raises: 137 No particular raises. 138 """ 139 parcial_obj_dict = ( 140 parcial_obj_dict if parcial_obj_dict is not None else {}) 141 142 url_str = "rest/%s/options/" % (model_class.lower(), ) 143 if field is not None: 144 url_str = url_str + field 145 146 # The auth header os set as temp to be used locally on this function, 147 # if it substitute the auth_header of the call it will cause 148 # error due to microservice logged and auth_header provided. 149 temp_auth_header = self._check_auth_header(auth_header=auth_header) 150 hash_dict = FillOptionsNoDataCacheHash( 151 authorization=temp_auth_header, model_class=model_class) 152 153 # Check if parameters passed on the request are empty, this will 154 # make it possible to use the cache. 155 are_parameters_empty = not parcial_obj_dict and field is None 156 if are_parameters_empty and use_disk_cache: 157 cache_value = default_cache.get(hash_dict) 158 if cache_value is not None: 159 return cache_value 160 161 return_value = self.request_post( 162 url=url_str, data=parcial_obj_dict, 163 auth_header=auth_header) 164 165 # If cache is to be used, than set it for the next call. 166 if are_parameters_empty and use_disk_cache: 167 default_cache.set(hash_dict=hash_dict, value=return_value) 168 return return_value 169 170 def list_options(self, model_class: str, auth_header: dict) -> dict: 171 """Return options to render list views. 172 173 This function send partial object data and return options to finish 174 object filling. 175 176 Args: 177 model_class (str): 178 Model class to check search parameters. 179 auth_header (dict): 180 Auth header to substitute the microservice original 181 at the request (user impersonation). 182 183 Returns: 184 Dictionary with keys: 185 - **default_list_fields:** Default list field defined on the 186 application backend. 187 - **field_descriptions:** Description of the fields associated 188 with the model class. 189 190 Raises: 191 No particular raise. 192 """ 193 url_str = "rest/{basename}/list-options/".format( 194 basename=model_class.lower()) 195 return self.request_get( 196 url=url_str, auth_header=auth_header) 197 198 def retrieve_options(self, model_class: str, 199 auth_header: dict = None) -> dict: 200 """Return options to render retrieve views. 201 202 Return information of the field sets that can be used to create 203 frontend site. It also return a `verbose_field` which can be used 204 to create the title of the page substituing the values with 205 information of the object. 206 207 Args: 208 model_class (str): 209 Model class to check search parameters. 210 auth_header (dict, optional): 211 Auth header to substitute the microservice original 212 at the request (user impersonation). 213 214 Returns: 215 Return a dictionary with keys: 216 - **verbose_field:** String sugesting how the title of the 217 retrieve might be created. It will use Python format 218 information ex.: `'{pk} | {description}'`. 219 - **fieldset:** An dictionary with organization of data, 220 setting field sets that could be grouped toguether in 221 tabs. 222 223 Raises: 224 No particular raises. 225 """ 226 url_str = "rest/{basename}/retrieve-options/".format( 227 basename=model_class.lower()) 228 return self.request_get( 229 url=url_str, auth_header=auth_header) 230 231 def fill_validation(self, model_class: str, parcial_obj_dict: dict = None, 232 field: str = None, auth_header: dict = None, 233 user_type: str = 'api') -> dict: 234 """Return options for object fields. 235 236 This function send partial object data and return options to finish 237 object filling. 238 239 Args: 240 model_class (str): 241 Model class to check search parameters. 242 auth_header (dict, optional): 243 Auth header to substitute the microservice original 244 at the request (user impersonation). 245 parcial_obj_dict (dict, optional): 246 Partial object data to be validated by the backend. 247 field (str, optional): 248 Set a specific field to be validated if implemented. 249 user_type (str): 250 Set the type of user is requesting fill validation. It is 251 possible to set `api` and `gui`. Gui user_type will return 252 fields listed in gui_readonly as read-only fields to 253 facilitate navigation. 254 255 Returns: 256 Return a dictionary with keys: 257 - **field_descriptions:** Same of fill_options, but setting as 258 read_only=True fields listed on gui_readonly if 259 user_type='gui'. 260 - **gui_readonly:** Return a list of fields that will be 261 considered as read-only if user_type='gui' is requested. 262 263 Raises: 264 No particular raises. 265 """ 266 parcial_obj_dict = ( 267 parcial_obj_dict if parcial_obj_dict is not None else {}) 268 269 url_str = "rest/{basename}/retrieve-options/".format( 270 basename=model_class.lower()) 271 params = {"user_type": user_type} 272 if field is not None: 273 params["field"] = field 274 return self.request_post( 275 url=url_str, auth_header=auth_header, data=parcial_obj_dict, 276 parameters=params)
Class for infomation calls.
26 def search_options(self, model_class: str, 27 auth_header: dict = None) -> dict: 28 """Return search options. 29 30 DEPRECATED Use `list_options` function instead. 31 32 Return information of the fields including available options for 33 options fields and model associated with the foreign key. 34 35 Args: 36 model_class (str): 37 Model class to check search parameters. 38 auth_header (dict, optional): 39 Auth header to substitute the microservice original 40 at the request (user impersonation). 41 42 Returns: 43 Return a dictionary with field names as keys and information of 44 them as values. Information at values: 45 - **primary_key [bool]:**: Boolean indicating if field is part 46 of model_class primary key. 47 - **column [str]:**: Name of the column. 48 - **column__verbose [str]:** Name of the column translated using 49 Pumpwood I8s. 50 - **help_text [str]:** Help text associated with column. 51 - **help_text__verbose [str]:** Help text associated with column 52 translated using Pumpwood I8s. 53 - **type [str]:** Python type associated with the column. 54 - **nullable [bool]:** If field can be set as null (None). 55 - **read_only [bool]:** If field is marked as read-only. Passsing 56 information for this field will not be used in save end-point. 57 - **default [any]:** Default value of the field if not set using 58 save end-poin. 59 - **unique [bool]:** If the there is a constrain in database 60 setting this field to be unique. 61 - **extra_info:** Some extra informations used to pass associated 62 model class for foreign key and related fields. 63 - **in [dict]:** At options fields, have their options listed in 64 `in` keys. It will return the values as key and de description 65 and description__verbose (translated by Pumpwood I8s) 66 as values. 67 - **partition:** At pk field, this key indicates if the database 68 if partitioned. Partitioned will perform better in queries if 69 partition is used on filter or exclude clauses. If table has 70 more than one level o partition, at least the first one must 71 be used when retrieving data. 72 73 Raises: 74 No particular raises. 75 """ 76 url_str = "rest/%s/options/" % (model_class.lower(), ) 77 return self.request_get(url=url_str, auth_header=auth_header)
Return search options.
DEPRECATED Use list_options function instead.
Return information of the fields including available options for options fields and model associated with the foreign key.
Arguments:
- model_class (str): Model class to check search parameters.
- auth_header (dict, optional): Auth header to substitute the microservice original at the request (user impersonation).
Returns:
Return a dictionary with field names as keys and information of them as values. Information at values:
- primary_key [bool]:: Boolean indicating if field is part of model_class primary key.
- column [str]:: Name of the column.
- column__verbose [str]: Name of the column translated using Pumpwood I8s.
- help_text [str]: Help text associated with column.
- help_text__verbose [str]: Help text associated with column translated using Pumpwood I8s.
- type [str]: Python type associated with the column.
- nullable [bool]: If field can be set as null (None).
- read_only [bool]: If field is marked as read-only. Passsing information for this field will not be used in save end-point.
- default [any]: Default value of the field if not set using save end-poin.
- unique [bool]: If the there is a constrain in database setting this field to be unique.
- extra_info: Some extra informations used to pass associated model class for foreign key and related fields.
- in [dict]: At options fields, have their options listed in
inkeys. It will return the values as key and de description and description__verbose (translated by Pumpwood I8s) as values.- partition: At pk field, this key indicates if the database if partitioned. Partitioned will perform better in queries if partition is used on filter or exclude clauses. If table has more than one level o partition, at least the first one must be used when retrieving data.
Raises:
- No particular raises.
79 def fill_options(self, model_class, parcial_obj_dict: dict = None, 80 field: str = None, auth_header: dict = None, 81 use_disk_cache: bool = True): 82 """Return options for object fields. 83 84 DEPRECATED Use `fill_validation` function instead. 85 86 This function send partial object data and return options to finish 87 object filling. 88 89 Args: 90 model_class (str): 91 Model class to check search parameters. 92 auth_header (dict, optional): 93 Auth header to substitute the microservice original 94 at the request (user impersonation). 95 parcial_obj_dict (dict, optional): 96 Partial object that is sent to backend for validation and 97 update fill options according to values passed for each field. 98 field (str, optional): 99 Restrict validation for a specific field if implemented. 100 use_disk_cache (bool): 101 If it will use the disk cache to retrieve information 102 from fill options if it was not passed partial object 103 data (`parcial_obj_dict=None`) or field (`field=None`). 104 105 Returns: 106 Return a dictionary with field names as keys and information of 107 them as values. Information at values: 108 - **primary_key [bool]:**: Boolean indicating if field is part 109 of model_class primary key. 110 - **column [str]:**: Name of the column. 111 - **column__verbose [str]:** Name of the column translated using 112 Pumpwood I8s. 113 - **help_text [str]:** Help text associated with column. 114 - **help_text__verbose [str]:** Help text associated with column 115 translated using Pumpwood I8s. 116 - **type [str]:** Python type associated with the column. 117 - **nullable [bool]:** If field can be set as null (None). 118 - **read_only [bool]:** If field is marked as read-only. Passsing 119 information for this field will not be used in save end-point. 120 - **default [any]:** Default value of the field if not set using 121 save end-poin. 122 - **unique [bool]:** If the there is a constrain in database 123 setting this field to be unique. 124 - **extra_info:** Some extra informations used to pass associated 125 model class for foreign key and related fields. 126 - **in [dict]:** At options fields, have their options listed in 127 `in` keys. It will return the values as key and de description 128 and description__verbose (translated by Pumpwood I8s) 129 as values. 130 - **partition:** At pk field, this key indicates if the database 131 if partitioned. Partitioned will perform better in queries if 132 partition is used on filter or exclude clauses. If table has 133 more than one level o partition, at least the first one must 134 be used when retrieving data. 135 136 Raises: 137 No particular raises. 138 """ 139 parcial_obj_dict = ( 140 parcial_obj_dict if parcial_obj_dict is not None else {}) 141 142 url_str = "rest/%s/options/" % (model_class.lower(), ) 143 if field is not None: 144 url_str = url_str + field 145 146 # The auth header os set as temp to be used locally on this function, 147 # if it substitute the auth_header of the call it will cause 148 # error due to microservice logged and auth_header provided. 149 temp_auth_header = self._check_auth_header(auth_header=auth_header) 150 hash_dict = FillOptionsNoDataCacheHash( 151 authorization=temp_auth_header, model_class=model_class) 152 153 # Check if parameters passed on the request are empty, this will 154 # make it possible to use the cache. 155 are_parameters_empty = not parcial_obj_dict and field is None 156 if are_parameters_empty and use_disk_cache: 157 cache_value = default_cache.get(hash_dict) 158 if cache_value is not None: 159 return cache_value 160 161 return_value = self.request_post( 162 url=url_str, data=parcial_obj_dict, 163 auth_header=auth_header) 164 165 # If cache is to be used, than set it for the next call. 166 if are_parameters_empty and use_disk_cache: 167 default_cache.set(hash_dict=hash_dict, value=return_value) 168 return return_value
Return options for object fields.
DEPRECATED Use fill_validation function instead.
This function send partial object data and return options to finish object filling.
Arguments:
- model_class (str): Model class to check search parameters.
- auth_header (dict, optional): Auth header to substitute the microservice original at the request (user impersonation).
- parcial_obj_dict (dict, optional): Partial object that is sent to backend for validation and update fill options according to values passed for each field.
- field (str, optional): Restrict validation for a specific field if implemented.
- use_disk_cache (bool): If it will use the disk cache to retrieve information
from fill options if it was not passed partial object
data (
parcial_obj_dict=None) or field (field=None).
Returns:
Return a dictionary with field names as keys and information of them as values. Information at values:
- primary_key [bool]:: Boolean indicating if field is part of model_class primary key.
- column [str]:: Name of the column.
- column__verbose [str]: Name of the column translated using Pumpwood I8s.
- help_text [str]: Help text associated with column.
- help_text__verbose [str]: Help text associated with column translated using Pumpwood I8s.
- type [str]: Python type associated with the column.
- nullable [bool]: If field can be set as null (None).
- read_only [bool]: If field is marked as read-only. Passsing information for this field will not be used in save end-point.
- default [any]: Default value of the field if not set using save end-poin.
- unique [bool]: If the there is a constrain in database setting this field to be unique.
- extra_info: Some extra informations used to pass associated model class for foreign key and related fields.
- in [dict]: At options fields, have their options listed in
inkeys. It will return the values as key and de description and description__verbose (translated by Pumpwood I8s) as values.- partition: At pk field, this key indicates if the database if partitioned. Partitioned will perform better in queries if partition is used on filter or exclude clauses. If table has more than one level o partition, at least the first one must be used when retrieving data.
Raises:
- No particular raises.
170 def list_options(self, model_class: str, auth_header: dict) -> dict: 171 """Return options to render list views. 172 173 This function send partial object data and return options to finish 174 object filling. 175 176 Args: 177 model_class (str): 178 Model class to check search parameters. 179 auth_header (dict): 180 Auth header to substitute the microservice original 181 at the request (user impersonation). 182 183 Returns: 184 Dictionary with keys: 185 - **default_list_fields:** Default list field defined on the 186 application backend. 187 - **field_descriptions:** Description of the fields associated 188 with the model class. 189 190 Raises: 191 No particular raise. 192 """ 193 url_str = "rest/{basename}/list-options/".format( 194 basename=model_class.lower()) 195 return self.request_get( 196 url=url_str, auth_header=auth_header)
Return options to render list views.
This function send partial object data and return options to finish object filling.
Arguments:
- model_class (str): Model class to check search parameters.
- auth_header (dict): Auth header to substitute the microservice original at the request (user impersonation).
Returns:
Dictionary with keys:
- default_list_fields: Default list field defined on the application backend.
- field_descriptions: Description of the fields associated with the model class.
Raises:
- No particular raise.
198 def retrieve_options(self, model_class: str, 199 auth_header: dict = None) -> dict: 200 """Return options to render retrieve views. 201 202 Return information of the field sets that can be used to create 203 frontend site. It also return a `verbose_field` which can be used 204 to create the title of the page substituing the values with 205 information of the object. 206 207 Args: 208 model_class (str): 209 Model class to check search parameters. 210 auth_header (dict, optional): 211 Auth header to substitute the microservice original 212 at the request (user impersonation). 213 214 Returns: 215 Return a dictionary with keys: 216 - **verbose_field:** String sugesting how the title of the 217 retrieve might be created. It will use Python format 218 information ex.: `'{pk} | {description}'`. 219 - **fieldset:** An dictionary with organization of data, 220 setting field sets that could be grouped toguether in 221 tabs. 222 223 Raises: 224 No particular raises. 225 """ 226 url_str = "rest/{basename}/retrieve-options/".format( 227 basename=model_class.lower()) 228 return self.request_get( 229 url=url_str, auth_header=auth_header)
Return options to render retrieve views.
Return information of the field sets that can be used to create
frontend site. It also return a verbose_field which can be used
to create the title of the page substituing the values with
information of the object.
Arguments:
- model_class (str): Model class to check search parameters.
- auth_header (dict, optional): Auth header to substitute the microservice original at the request (user impersonation).
Returns:
Return a dictionary with keys:
- verbose_field: String sugesting how the title of the retrieve might be created. It will use Python format information ex.:
'{pk} | {description}'.- fieldset: An dictionary with organization of data, setting field sets that could be grouped toguether in tabs.
Raises:
- No particular raises.
231 def fill_validation(self, model_class: str, parcial_obj_dict: dict = None, 232 field: str = None, auth_header: dict = None, 233 user_type: str = 'api') -> dict: 234 """Return options for object fields. 235 236 This function send partial object data and return options to finish 237 object filling. 238 239 Args: 240 model_class (str): 241 Model class to check search parameters. 242 auth_header (dict, optional): 243 Auth header to substitute the microservice original 244 at the request (user impersonation). 245 parcial_obj_dict (dict, optional): 246 Partial object data to be validated by the backend. 247 field (str, optional): 248 Set a specific field to be validated if implemented. 249 user_type (str): 250 Set the type of user is requesting fill validation. It is 251 possible to set `api` and `gui`. Gui user_type will return 252 fields listed in gui_readonly as read-only fields to 253 facilitate navigation. 254 255 Returns: 256 Return a dictionary with keys: 257 - **field_descriptions:** Same of fill_options, but setting as 258 read_only=True fields listed on gui_readonly if 259 user_type='gui'. 260 - **gui_readonly:** Return a list of fields that will be 261 considered as read-only if user_type='gui' is requested. 262 263 Raises: 264 No particular raises. 265 """ 266 parcial_obj_dict = ( 267 parcial_obj_dict if parcial_obj_dict is not None else {}) 268 269 url_str = "rest/{basename}/retrieve-options/".format( 270 basename=model_class.lower()) 271 params = {"user_type": user_type} 272 if field is not None: 273 params["field"] = field 274 return self.request_post( 275 url=url_str, auth_header=auth_header, data=parcial_obj_dict, 276 parameters=params)
Return options for object fields.
This function send partial object data and return options to finish object filling.
Arguments:
- model_class (str): Model class to check search parameters.
- auth_header (dict, optional): Auth header to substitute the microservice original at the request (user impersonation).
- parcial_obj_dict (dict, optional): Partial object data to be validated by the backend.
- field (str, optional): Set a specific field to be validated if implemented.
- user_type (str): Set the type of user is requesting fill validation. It is
possible to set
apiandgui. Gui user_type will return fields listed in gui_readonly as read-only fields to facilitate navigation.
Returns:
Return a dictionary with keys:
- field_descriptions: Same of fill_options, but setting as read_only=True fields listed on gui_readonly if user_type='gui'.
- gui_readonly: Return a list of fields that will be considered as read-only if user_type='gui' is requested.
Raises:
- No particular raises.