afscgap.client
Logic for making actual HTTP requests and managaing pagination when interfacing with the NOAA-run AFSC GAP API.
(c) 2023 Regents of University of California / The Eric and Wendy Schmidt Center for Data Science and the Environment at UC Berkeley.
This file is part of afscgap released under the BSD 3-Clause License. See LICENSE.md.
1""" 2Logic for making actual HTTP requests and managaing pagination when interfacing 3with the NOAA-run AFSC GAP API. 4 5(c) 2023 Regents of University of California / The Eric and Wendy Schmidt Center 6for Data Science and the Environment at UC Berkeley. 7 8This file is part of afscgap released under the BSD 3-Clause License. See 9LICENSE.md. 10""" 11import copy 12import json 13import queue 14import typing 15 16import afscgap.convert 17import afscgap.cursor 18import afscgap.http_util 19import afscgap.model 20import afscgap.query_util 21 22from afscgap.typesdef import OPT_FLOAT 23from afscgap.typesdef import OPT_INT 24from afscgap.typesdef import OPT_REQUESTOR 25from afscgap.typesdef import OPT_STR 26 27DEFAULT_DOMAIN = 'https://apps-st.fisheries.noaa.gov' 28DEFAULT_URL = DEFAULT_DOMAIN + '/ods/foss/afsc_groundfish_survey/' 29 30 31def build_api_cursor(params: dict, limit: OPT_INT = None, 32 start_offset: OPT_INT = None, filter_incomplete: bool = False, 33 requestor: OPT_REQUESTOR = None, 34 base_url: OPT_STR = None) -> afscgap.cursor.Cursor: 35 """Build a cursor which will iterate over API service results. 36 37 Args: 38 params: Dictionary of filters to apply to the query where a value of 39 None means no filter should be applied on that field. 40 limit: The maximum number of records to return per page. 41 start_offset: The number of records being skipped (number of records 42 prior to query_url). 43 filter_incomplete: Flag indicating if incomplete records should be 44 silently filtered. If true, they will not be returned during 45 iteration and placed in the queue at get_invalid(). If false, 46 they will be returned and those incomplete records' 47 get_complete() will return false. Defaults to false. 48 requestor: Strategy to make HTTP GET requests. If None, will default 49 to requests.get. 50 base_url: The URL at which the API service can be found or None to use 51 a default. Defaults to None. 52 53 Returns: 54 Cursor which iterates over the results from the API service. 55 """ 56 params_safe = copy.deepcopy(params) 57 params_safe['date_time'] = afscgap.convert.convert_from_iso8601( 58 params_safe['date_time'] 59 ) 60 params_ords = afscgap.query_util.interpret_query_to_ords(params_safe) 61 62 query_url = get_query_url(params_ords, base=base_url) 63 64 return ApiServiceCursor( 65 query_url, 66 limit=limit, 67 start_offset=start_offset, 68 requestor=requestor, 69 filter_incomplete=filter_incomplete 70 ) 71 72 73def get_query_url(params: dict, base: OPT_STR = None) -> str: 74 """Get the URL at which a query can be made. 75 76 Args: 77 params: Dictionary of filters to apply to the query where a value of 78 None means no filter should be applied on that field. 79 base: The URL at which the API service can be found. If None, will use 80 DEFAULT_URL. Defaults to None. 81 82 Returns: 83 URL at which an HTTP GET request can be made to execute the desired 84 query. 85 """ 86 if base is None: 87 base = DEFAULT_URL 88 89 all_items = params.items() 90 items_included = filter(lambda x: x[1] is not None, all_items) 91 included_dict = dict(items_included) 92 included_json = json.dumps(included_dict) 93 94 return '%s?q=%s' % (base, included_json) 95 96 97class ApiServiceCursor(afscgap.cursor.Cursor): 98 """Object for requests and interpreting API service results.""" 99 100 def __init__(self, query_url: str, limit: OPT_INT = None, 101 start_offset: OPT_INT = None, filter_incomplete: bool = False, 102 requestor: OPT_REQUESTOR = None): 103 """Create a new cursor to manage a request. 104 105 Args: 106 query_url: The URL for the query without pagination information. 107 limit: The maximum number of records to return per page. 108 start_offset: The number of records being skipped (number of records 109 prior to query_url). 110 filter_incomplete: Flag indicating if incomplete records should be 111 silently filtered. If true, they will not be returned during 112 iteration and placed in the queue at get_invalid(). If false, 113 they will be returned and those incomplete records' 114 get_complete() will return false. Defaults to false. 115 requestor: Strategy to make HTTP GET requests. If None, will default 116 to requests.get. 117 """ 118 self._query_url = query_url 119 self._limit = limit 120 self._start_offset = start_offset 121 self._filter_incomplete = filter_incomplete 122 self._queue: queue.Queue[afscgap.model.Record] = queue.Queue() 123 self._invalid_queue: queue.Queue[dict] = queue.Queue() 124 self._done = False 125 126 if requestor: 127 self._request_strategy = requestor 128 else: 129 self._request_strategy = afscgap.http_util.build_requestor() 130 131 self._next_url = self.get_page_url() 132 133 def get_base_url(self) -> str: 134 """Get the URL at which the first page of query results can be found. 135 136 Returns: 137 The URL for the query without pagination information. 138 """ 139 return self._query_url 140 141 def get_limit(self) -> OPT_INT: 142 """Get the page size limit. 143 144 Returns: 145 The maximum number of records to return per page. 146 """ 147 return self._limit 148 149 def get_start_offset(self) -> OPT_INT: 150 """Get the number of inital records to ignore. 151 152 Returns: 153 The number of records being skipped at the start of the result set. 154 """ 155 return self._start_offset 156 157 def get_filtering_incomplete(self) -> bool: 158 """Determine if this cursor is silently filtering incomplete records. 159 160 Returns: 161 Flag indicating if incomplete records should be silently filtered. 162 If true, they will not be returned during iteration and placed in 163 the queue at get_invalid(). If false, they will be returned and 164 those incomplete records' get_complete() will return false. 165 """ 166 return self._filter_incomplete 167 168 def get_page_url(self, offset: OPT_INT = None, 169 limit: OPT_INT = None) -> str: 170 """Get a URL at which a page can be found using this cursor's base url. 171 172 Args: 173 offset: The number of records to skip prior to the page. 174 limit: The maximum number of records to return in the page. 175 Returns: 176 URL at which the requested page can be found. 177 """ 178 179 if offset is None: 180 offset = self._start_offset 181 182 if limit is None: 183 limit = self._limit 184 185 pagination_params = [] 186 187 if offset: 188 pagination_params.append('offset=%d' % offset) 189 190 if limit: 191 pagination_params.append('limit=%d' % limit) 192 193 if len(pagination_params) > 0: 194 pagination_params_str = '&'.join(pagination_params) 195 return self._query_url + '&' + pagination_params_str 196 else: 197 return self._query_url 198 199 def get_page(self, offset: OPT_INT = None, 200 limit: OPT_INT = None, 201 ignore_invalid: bool = False) -> typing.List[afscgap.model.Record]: 202 """Get a page using this cursor's base url. 203 204 Args: 205 offset: The number of records to skip prior to the page. 206 limit: The maximum number of records to return in the page. 207 ignore_invalid: Flag indicating how to handle invalid records. If 208 true, will silently throw away records which could not be 209 parsed. If false, will raise an exception if a record can not 210 be parsed. 211 212 Returns: 213 Results from the page which, regardless of ignore_invalid, may 214 contain a mixture of complete and incomplete records. 215 """ 216 url = self.get_page_url(offset, limit) 217 218 result = self._request_strategy(url) 219 afscgap.http_util.check_result(result) 220 221 result_parsed = result.json() 222 items_raw = result_parsed['items'] 223 224 parsed_maybe = map(try_parse, items_raw) 225 parsed_with_none = map(lambda x: x.get_parsed(), parsed_maybe) 226 227 if ignore_invalid: 228 parsed_no_none = filter(lambda x: x is not None, parsed_with_none) 229 return list(parsed_no_none) # type: ignore 230 else: 231 parsed = list(parsed_with_none) 232 233 if None in parsed: 234 raise RuntimeError('Encountered invalid record.') 235 236 return parsed # type: ignore 237 238 def get_invalid(self) -> 'queue.Queue[dict]': 239 """Get a queue of invalid / incomplete records found so far. 240 241 Returns: 242 Queue with dictionaries containing the raw data returned from the 243 API that did not have valid values for all required fields. Note 244 that this will include incomplete records as well if 245 get_filtering_incomplete() is true and will not contain incomplete 246 records otherwise. 247 """ 248 return self._invalid_queue 249 250 def to_dicts(self) -> typing.Iterable[dict]: 251 """Create an iterator which converts Records to dicts. 252 253 Returns: 254 Iterator which returns dictionaries instead of Record objects but 255 has otherwise the same beahavior as iterating in this Cursor 256 directly. 257 """ 258 return map(lambda x: x.to_dict(), self) 259 260 def get_next(self) -> typing.Optional[afscgap.model.Record]: 261 """Get the next value for this Cursor. 262 263 Returns: 264 The next value waiting if cached in the cursor's results queue or 265 as just retrieved from a new page gathered by HTTP request. Will 266 return None if no remain. 267 """ 268 self._load_next_page() 269 270 if self._queue.empty(): 271 return None 272 else: 273 return self._queue.get() 274 275 def _load_next_page(self): 276 """Request and parse additional results if they exist. 277 278 Request and parse the next page(s) of results if one exists, putting it 279 into the waiting results queues. Note that this will contiune to 280 request new pages until finding valid results or no results remain. 281 """ 282 while self._queue.empty() and not self._done: 283 self._queue_next_page() 284 285 def _queue_next_page(self): 286 """Request the next page of waiting results. 287 288 Request the next page of waiting results, putting newly returned data 289 into the waiting queues and updating the next url / done internal 290 state in the process. 291 """ 292 if self._done: 293 return 294 295 result = self._request_strategy(self._next_url) 296 afscgap.http_util.check_result(result) 297 298 result_parsed = result.json() 299 300 items_raw = result_parsed['items'] 301 302 items_parsed = map(try_parse, items_raw) 303 304 # If we are filtering incomplete records, we will not allow incomplete. 305 allow_incomplete = not self._filter_incomplete 306 307 for parse_result in items_parsed: 308 if parse_result.meets_requirements(allow_incomplete): 309 self._queue.put(parse_result.get_parsed()) 310 else: 311 self._invalid_queue.put(parse_result.get_raw_record()) 312 313 next_url = self._find_next_url(result_parsed) 314 self._done = next_url is None 315 self._next_url = next_url 316 317 def _find_next_url(self, target: dict) -> OPT_STR: 318 """Look for the URL with the next page of results if it exists. 319 320 Args: 321 target: The raw complete parsed JSON response from the API. 322 323 Returns: 324 The URL where the next page of results can be found via HTTP GET 325 request or None if target indicates that no results remain. 326 """ 327 if not target['hasMore']: 328 return None 329 330 links = target['links'] 331 matching = filter(lambda x: x['rel'] == 'next', links) 332 matching_hrefs = map(lambda x: x['href'], matching) 333 hrefs_realized = list(matching_hrefs) 334 335 if len(hrefs_realized) == 0: 336 raise RuntimeError('Could not find next URL but hasMore was true.') 337 338 return hrefs_realized[0] 339 340 341class ApiRecord(afscgap.model.Record): 342 """Data structure describing a single record returned by the API.""" 343 344 def __init__(self, year: float, srvy: str, survey: str, survey_id: float, 345 cruise: float, haul: float, stratum: float, station: str, 346 vessel_name: str, vessel_id: float, date_time: str, latitude_dd: float, 347 longitude_dd: float, species_code: float, common_name: str, 348 scientific_name: str, taxon_confidence: str, cpue_kgha: OPT_FLOAT, 349 cpue_kgkm2: OPT_FLOAT, cpue_kg1000km2: OPT_FLOAT, cpue_noha: OPT_FLOAT, 350 cpue_nokm2: OPT_FLOAT, cpue_no1000km2: OPT_FLOAT, weight_kg: OPT_FLOAT, 351 count: OPT_FLOAT, bottom_temperature_c: OPT_FLOAT, 352 surface_temperature_c: OPT_FLOAT, depth_m: float, 353 distance_fished_km: float, net_width_m: OPT_FLOAT, 354 net_height_m: OPT_FLOAT, area_swept_ha: float, duration_hr: float, 355 tsn: OPT_INT, ak_survey_id: int): 356 """Create a new record. 357 358 Args: 359 year: Year for the survey in which this observation was made. 360 srvy: The name of the survey in which this observation was made. 361 NBS (N Bearing Sea), EBS (SE Bearing Sea), BSS (Bearing Sea 362 Slope), or GOA (Gulf of Alaska) 363 survey: Long form description of the survey in which the observation 364 was made. 365 survey_id: Unique numeric ID for the survey. 366 cruise: An ID uniquely identifying the cruise in which the 367 observation was made. Multiple cruises in a survey. 368 haul: An ID uniquely identifying the haul in which this observation 369 was made. Multiple hauls per cruises. 370 stratum: Unique ID for statistical area / survey combination as 371 described in the metadata or 0 if an experimental tow. 372 station: Station associated with the survey. 373 vessel_name: Unique ID describing the vessel that made this 374 observation. This is left as a string but, in practice, is 375 likely numeric. 376 vessel_id: Name of the vessel at the time the observation was made 377 with multiple names potentially associated with a vessel ID. 378 date_time: The date and time of the haul which has been attempted to 379 be transformed to an ISO 8601 string without timezone info. 380 If it couldn’t be transformed, the original string. 381 latitude_dd: Latitude in decimal degrees associated with the haul. 382 longitude_dd: Longitude in decimal degrees associated with the haul. 383 species_code: Unique ID associated with the species observed. 384 common_name: The “common name” associated with the species observed. 385 scientific_name: The “scientific name” associated with the species 386 observed. 387 taxon_confidence: Confidence flag regarding ability to identify 388 species (High, Moderate, Low). In practice, this can also be 389 Unassessed. 390 cpue_kgha: Catch weight divided by net area (kg / hectares) if 391 available. None if could not interpret as a float. 392 cpue_kgkm2: Catch weight divided by net area (kg / km^2) if 393 available. None if could not interpret as a float. 394 cpue_kg1000km2: Catch weight divided by net area (kg / km^2 * 1000) 395 if available. See metadata. None if could not interpret as a 396 float. 397 cpue_noha: Catch number divided by net sweep area if available 398 (count / hectares). None if could not interpret as a float. 399 cpue_nokm2: Catch number divided by net sweep area if available 400 (count / km^2). See metadata. None if could not interpret as a 401 float. 402 cpue_no1000km2: Catch number divided by net sweep area if available 403 (count / km^2 * 1000). See metadata. None if could not interpret 404 as a float. 405 weight_kg: Taxon weight (kg) if available. See metadata. None if 406 could not interpret as a float. 407 count: Total number of organism individuals in haul. None if could 408 not interpret as a float. 409 bottom_temperature_c: Bottom temperature associated with observation 410 if available in Celsius. None if not given or could not 411 interpret as a float. 412 surface_temperature_c: Surface temperature associated with 413 observation if available in Celsius. None if not given or could 414 not interpret as a float. 415 depth_m: Depth of the bottom in meters. 416 distance_fished_km: Distance of the net fished as km. 417 net_width_m: Distance of the net fished as m. 418 net_height_m: Height of the net fished as m. 419 area_swept_ha: Area covered by the net while fishing in hectares. 420 duration_hr: Duration of the haul as number of hours. 421 tsn: Taxonomic information system species code. 422 ak_survey_id: AK identifier for the survey. 423 """ 424 self._year = year 425 self._srvy = srvy 426 self._survey = survey 427 self._survey_id = survey_id 428 self._cruise = cruise 429 self._haul = haul 430 self._stratum = stratum 431 self._station = station 432 self._vessel_name = vessel_name 433 self._vessel_id = vessel_id 434 self._date_time = date_time 435 self._latitude_dd = latitude_dd 436 self._longitude_dd = longitude_dd 437 self._species_code = species_code 438 self._common_name = common_name 439 self._scientific_name = scientific_name 440 self._taxon_confidence = taxon_confidence 441 self._cpue_kgha = cpue_kgha 442 self._cpue_kgkm2 = cpue_kgkm2 443 self._cpue_kg1000km2 = cpue_kg1000km2 444 self._cpue_noha = cpue_noha 445 self._cpue_nokm2 = cpue_nokm2 446 self._cpue_no1000km2 = cpue_no1000km2 447 self._weight_kg = weight_kg 448 self._count = count 449 self._bottom_temperature_c = bottom_temperature_c 450 self._surface_temperature_c = surface_temperature_c 451 self._depth_m = depth_m 452 self._distance_fished_km = distance_fished_km 453 self._net_width_m = net_width_m 454 self._net_height_m = net_height_m 455 self._area_swept_ha = area_swept_ha 456 self._duration_hr = duration_hr 457 self._tsn = tsn 458 self._ak_survey_id = ak_survey_id 459 460 def get_year(self) -> float: 461 """Get the field labeled as year in the API. 462 463 Returns: 464 Year for the survey in which this observation was made. 465 """ 466 return self._year 467 468 def get_srvy(self) -> str: 469 """Get the field labeled as srvy in the API. 470 471 Returns: 472 The name of the survey in which this observation was made. NBS (N 473 Bearing Sea), EBS (SE Bearing Sea), BSS (Bearing Sea Slope), or GOA 474 (Gulf of Alaska) 475 """ 476 return self._srvy 477 478 def get_survey(self) -> str: 479 """Get the field labeled as survey in the API. 480 481 Returns: 482 Long form description of the survey in which the observation was 483 made. 484 """ 485 return self._survey 486 487 def get_survey_id(self) -> float: 488 """Get the field labeled as survey_id in the API. 489 490 Returns: 491 Unique numeric ID for the survey. 492 """ 493 return self._survey_id 494 495 def get_cruise(self) -> float: 496 """Get the field labeled as cruise in the API. 497 498 Returns: 499 An ID uniquely identifying the cruise in which the observation was 500 made. Multiple cruises in a survey. 501 """ 502 return self._cruise 503 504 def get_haul(self) -> float: 505 """Get the field labeled as haul in the API. 506 507 Returns: 508 An ID uniquely identifying the haul in which this observation was 509 made. Multiple hauls per cruises. 510 """ 511 return self._haul 512 513 def get_stratum(self) -> float: 514 """Get the field labeled as stratum in the API. 515 516 Returns: 517 Unique ID for statistical area / survey combination as described in 518 the metadata or 0 if an experimental tow. 519 """ 520 return self._stratum 521 522 def get_station(self) -> str: 523 """Get the field labeled as station in the API. 524 525 Returns: 526 Station associated with the survey. 527 """ 528 return self._station 529 530 def get_vessel_name(self) -> str: 531 """Get the field labeled as vessel_name in the API. 532 533 Returns: 534 Unique ID describing the vessel that made this observation. This is 535 left as a string but, in practice, is likely numeric. 536 """ 537 return self._vessel_name 538 539 def get_vessel_id(self) -> float: 540 """Get the field labeled as vessel_id in the API. 541 542 Returns: 543 Name of the vessel at the time the observation was made with 544 multiple names potentially associated with a vessel ID. 545 """ 546 return self._vessel_id 547 548 def get_date_time(self) -> str: 549 """Get the field labeled as date_time in the API. 550 551 Returns: 552 The date and time of the haul which has been attempted to be 553 transformed to an ISO 8601 string without timezone info. If it 554 couldn’t be transformed, the original string is reported. 555 """ 556 return self._date_time 557 558 def get_latitude(self, units: str = 'dd') -> float: 559 """Get the field labeled as latitude_dd in the API. 560 561 Args: 562 units: The units to return this value in. Only supported is dd for 563 degrees. Deafults to dd. 564 565 Returns: 566 Latitude in decimal degrees associated with the haul. 567 """ 568 return afscgap.model.assert_float_present( 569 afscgap.convert.convert_degrees(self._latitude_dd, units) 570 ) 571 572 def get_longitude(self, units: str = 'dd') -> float: 573 """Get the field labeled as longitude_dd in the API. 574 575 Args: 576 units: The units to return this value in. Only supported is dd for 577 degrees. Deafults to dd. 578 579 Returns: 580 Longitude in decimal degrees associated with the haul. 581 """ 582 return afscgap.model.assert_float_present( 583 afscgap.convert.convert_degrees(self._longitude_dd, units) 584 ) 585 586 def get_species_code(self) -> float: 587 """Get the field labeled as species_code in the API. 588 589 Returns: 590 Unique ID associated with the species observed. 591 """ 592 return self._species_code 593 594 def get_common_name(self) -> str: 595 """Get the field labeled as common_name in the API. 596 597 Returns: 598 The “common name” associated with the species observed. Example: 599 Pacific glass shrimp. 600 """ 601 return self._common_name 602 603 def get_scientific_name(self) -> str: 604 """Get the field labeled as scientific_name in the API. 605 606 Returns: 607 The “scientific name” associated with the species observed. Example: 608 Pasiphaea pacifica. 609 """ 610 return self._scientific_name 611 612 def get_taxon_confidence(self) -> str: 613 """Get the field labeled as taxon_confidence in the API. 614 615 Returns: 616 Confidence flag regarding ability to identify species (High, 617 Moderate, Low). In practice, this can also be Unassessed. 618 """ 619 return self._taxon_confidence 620 621 def get_cpue_weight_maybe(self, units: str = 'kg/ha') -> OPT_FLOAT: 622 """Get a field labeled as cpue_* in the API. 623 624 Args: 625 units: The desired units for the catch per unit effort. Options: 626 kg/ha, kg/km2, kg1000/km2. Defaults to kg/ha. 627 628 Returns: 629 Catch weight divided by net area (in given units) if available. See 630 metadata. None if could not interpret as a float. If an inferred 631 zero catch record, will be zero. 632 """ 633 return { 634 'kg/ha': self._cpue_kgha, 635 'kg/km2': self._cpue_kgkm2, 636 'kg1000/km2': self._cpue_kg1000km2 637 }[units] 638 639 def get_cpue_count_maybe(self, units: str = 'kg/ha') -> OPT_FLOAT: 640 """Get the field labeled as cpue_* in the API. 641 642 Get the catch per unit effort from the record with one of the following 643 units: kg/ha, kg/km2, kg1000/km2. 644 645 Args: 646 units: The desired units for the catch per unit effort. Options: 647 count/ha, count/km2, and count1000/km2. Defaults to count/ha. 648 649 Returns: 650 Catch weight divided by net area (in given units) if available. See 651 metadata. None if could not interpret as a float. If an inferred 652 zero catch record, will be zero. 653 """ 654 return { 655 'count/ha': self._cpue_noha, 656 'count/km2': self._cpue_nokm2, 657 'count1000/km2': self._cpue_no1000km2 658 }[units] 659 660 def get_weight_maybe(self, units: str = 'kg') -> OPT_FLOAT: 661 """Get the field labeled as weight_kg in the API. 662 663 Args: 664 units: The units in which the weight should be returned. Options are 665 g, kg for grams and kilograms respectively. Deafults to kg. 666 667 Returns: 668 Taxon weight if available. See metadata. None if could not 669 interpret as a float. If an inferred zero catch record, will be 670 zero. 671 """ 672 return afscgap.convert.convert_weight(self._weight_kg, units) 673 674 def get_count_maybe(self) -> OPT_FLOAT: 675 """Get the field labeled as count in the API. 676 677 Returns: 678 Total number of organism individuals in haul. None if could not 679 interpret as a float. 680 """ 681 return self._count 682 683 def get_bottom_temperature_maybe(self, units: str = 'c') -> OPT_FLOAT: 684 """Get the field labeled as bottom_temperature_c in the API. 685 686 Args: 687 units: The units in which the temperature should be returned. 688 Options: c or f for Celcius and Fahrenheit respectively. 689 Defaults to c. 690 691 Returns: 692 Bottom temperature associated with observation / inferrence if 693 available in desired units. None if not given or could not interpret 694 as a float. 695 """ 696 return afscgap.convert.convert_temperature( 697 self._bottom_temperature_c, 698 units 699 ) 700 701 def get_surface_temperature_maybe(self, units: str = 'c') -> OPT_FLOAT: 702 """Get the field labeled as surface_temperature_c in the API. 703 704 Args: 705 units: The units in which the temperature should be returned. 706 Options: c or f for Celcius and Fahrenheit respectively. 707 Defaults to c. 708 709 Returns: 710 Surface temperature associated with observation / inferrence if 711 available. None if not given or could not interpret as a float. 712 """ 713 return afscgap.convert.convert_temperature( 714 self._surface_temperature_c, 715 units 716 ) 717 718 def get_depth(self, units: str = 'm') -> float: 719 """Get the field labeled as depth_m in the API. 720 721 Args: 722 units: The units in which the distance should be returned. Options: 723 m or km for meters and kilometers respectively. Defaults to m. 724 725 Returns: 726 Depth of the bottom. 727 """ 728 return afscgap.model.assert_float_present( 729 afscgap.convert.convert_distance(self._depth_m, units) 730 ) 731 732 def get_distance_fished(self, units: str = 'm') -> float: 733 """Get the field labeled as distance_fished_km in the API. 734 735 Args: 736 units: The units in which the distance should be returned. Options: 737 m or km for meters and kilometers respectively. Defaults to m. 738 739 Returns: 740 Distance of the net fished. 741 """ 742 return afscgap.model.assert_float_present( 743 afscgap.convert.convert_distance( 744 self._distance_fished_km * 1000, 745 units 746 ) 747 ) 748 749 def get_net_width_maybe(self, units: str = 'm') -> OPT_FLOAT: 750 """Get the field labeled as net_width_m in the API. 751 752 Args: 753 units: The units in which the distance should be returned. Options: 754 m or km for meters and kilometers respectively. Defaults to m. 755 756 Returns: 757 Distance of the net fished or None if not given. 758 """ 759 return afscgap.convert.convert_distance( 760 self._net_width_m, 761 units 762 ) 763 764 def get_net_height_maybe(self, units: str = 'm') -> OPT_FLOAT: 765 """Get the field labeled as net_height_m in the API. 766 767 Args: 768 units: The units in which the distance should be returned. Options: 769 m or km for meters and kilometers respectively. Defaults to m. 770 771 Returns: 772 Height of the net fished or None if not given. 773 """ 774 return afscgap.convert.convert_distance( 775 self._net_height_m, 776 units 777 ) 778 779 def get_net_width(self, units: str = 'm') -> float: 780 """Get the field labeled as net_width_m in the API. 781 782 Args: 783 units: The units in which the distance should be returned. Options: 784 m or km for meters and kilometers respectively. Defaults to m. 785 786 Returns: 787 Distance of the net fished after asserting it is given. 788 """ 789 return afscgap.model.assert_float_present( 790 self.get_net_width_maybe(units=units) 791 ) 792 793 def get_net_height(self, units: str = 'm') -> float: 794 """Get the field labeled as net_height_m in the API. 795 796 Args: 797 units: The units in which the distance should be returned. Options: 798 m or km for meters and kilometers respectively. Defaults to m. 799 800 Returns: 801 Height of the net fished after asserting it is given. 802 """ 803 return afscgap.model.assert_float_present( 804 self.get_net_height_maybe(units=units) 805 ) 806 807 def get_area_swept(self, units: str = 'ha') -> float: 808 """Get the field labeled as area_swept_ha in the API. 809 810 Args: 811 units: The units in which the area should be returned. Options: 812 ha, m2, km2. Defaults to ha. 813 814 Returns: 815 Area covered by the net while fishing in desired units. 816 """ 817 return afscgap.model.assert_float_present( 818 afscgap.convert.convert_area(self._area_swept_ha, units) 819 ) 820 821 def get_duration(self, units: str = 'hr') -> float: 822 """Get the field labeled as duration_hr in the API. 823 824 Args: 825 units: The units in which the duration should be returned. Options: 826 day, hr, min. Defaults to hr. 827 828 Returns: 829 Duration of the haul. 830 """ 831 return afscgap.model.assert_float_present( 832 afscgap.convert.convert_time(self._duration_hr, units) 833 ) 834 835 def get_tsn(self) -> int: 836 """Get the field labeled as tsn in the API. 837 838 Returns: 839 Taxonomic information system species code. 840 """ 841 return afscgap.model.assert_int_present(self._tsn) 842 843 def get_tsn_maybe(self) -> OPT_INT: 844 """Get the field labeled as tsn in the API or None. 845 846 Returns: 847 Taxonomic information system species code if it could be parsed as 848 an int and None otherwise. 849 """ 850 return self._tsn 851 852 def get_ak_survey_id(self) -> int: 853 """Get the field labeled as ak_survey_id in the API. 854 855 Returns: 856 AK identifier for the survey. 857 """ 858 return self._ak_survey_id 859 860 def get_ak_survey_id_maybe(self) -> OPT_INT: 861 """Get the field labeled as ak_survey_id in the API. 862 863 Returns: 864 AK identifier for the survey or None if not given. 865 """ 866 return self._ak_survey_id 867 868 def get_cpue_weight(self, units: str = 'kg/ha') -> float: 869 """Get the value of field cpue_kgha with validity assert. 870 871 Args: 872 units: The desired units for the catch per unit effort. Options: 873 kg/ha, kg/km2, kg1000/km2. Defaults to kg/ha. 874 875 Raises: 876 AssertionError: Raised if this field was not given by the API or 877 could not be parsed as expected. 878 879 Returns: 880 Catch weight divided by net area (kg / hectares) if available. See 881 metadata. 882 """ 883 return afscgap.model.assert_float_present( 884 self.get_cpue_weight_maybe(units=units) 885 ) 886 887 def get_cpue_count(self, units: str = 'count/ha') -> float: 888 """Get the value of field cpue_noha with validity assert. 889 890 Args: 891 units: The desired units for the catch per unit effort. Options: 892 count/ha, count/km2, and count1000/km2. Defaults to count/ha. 893 894 Raises: 895 AssertionError: Raised if this field was not given by the API or 896 could not be parsed as expected. 897 898 Returns: 899 Catch number divided by net sweep area if available (count / 900 hectares). See metadata. 901 """ 902 return afscgap.model.assert_float_present( 903 self.get_cpue_count_maybe(units=units) 904 ) 905 906 def get_weight(self, units: str = 'kg') -> float: 907 """Get the value of field weight_kg with validity assert. 908 909 Args: 910 units: The units in which the weight should be returned. Options are 911 g, kg for grams and kilograms respectively. Deafults to kg. 912 913 Raises: 914 AssertionError: Raised if this field was not given by the API or 915 could not be parsed as expected. 916 917 Returns: 918 Taxon weight (kg) if available. See metadata. 919 """ 920 return afscgap.model.assert_float_present( 921 self.get_weight_maybe(units=units) 922 ) 923 924 def get_count(self) -> float: 925 """Get the value of field count with validity assert. 926 927 Raises: 928 AssertionError: Raised if this field was not given by the API or 929 could not be parsed as expected. 930 931 Returns: 932 Total number of organism individuals in haul. 933 """ 934 return afscgap.model.assert_float_present(self._count) 935 936 def get_bottom_temperature(self, units='c') -> float: 937 """Get the value of field bottom_temperature_c with validity assert. 938 939 Args: 940 units: The units in which the temperature should be returned. 941 Options: c or f for Celcius and Fahrenheit respectively. 942 Defaults to c. 943 944 Raises: 945 AssertionError: Raised if this field was not given by the API or 946 could not be parsed as expected. 947 948 Returns: 949 Bottom temperature associated with observation / inferrence if 950 available. 951 """ 952 return afscgap.model.assert_float_present( 953 self.get_bottom_temperature_maybe(units=units) 954 ) 955 956 def get_surface_temperature(self, units='c') -> float: 957 """Get the value of field surface_temperature_c with validity assert. 958 959 Args: 960 units: The units in which the temperature should be returned. 961 Options: c or f for Celcius and Fahrenheit respectively. 962 Defaults to c. 963 964 Raises: 965 AssertionError: Raised if this field was not given by the API or 966 could not be parsed as expected. 967 968 Returns: 969 Surface temperature associated with observation / inferrence if 970 available. 971 """ 972 return afscgap.model.assert_float_present( 973 self.get_surface_temperature_maybe(units=units) 974 ) 975 976 def is_complete(self) -> bool: 977 """Determine if this record has all of its values filled in. 978 979 Returns: 980 True if all optional fields have a parsed value with the expected 981 type and false otherwise. 982 """ 983 optional_fields = [ 984 self._cpue_kgha, 985 self._cpue_kgkm2, 986 self._cpue_kg1000km2, 987 self._cpue_noha, 988 self._cpue_nokm2, 989 self._cpue_no1000km2, 990 self._weight_kg, 991 self._count, 992 self._bottom_temperature_c, 993 self._surface_temperature_c, 994 self._tsn 995 ] 996 997 has_none = None in optional_fields 998 all_fields_present = not has_none 999 has_valid_date_time = afscgap.convert.is_iso8601(self._date_time) 1000 1001 return all_fields_present and has_valid_date_time 1002 1003 1004def parse_record(target: dict) -> afscgap.model.Record: 1005 """Parse a record from a returned item dictionary. 1006 1007 Args: 1008 target: The dictionary from which values should be read. 1009 1010 Raises: 1011 ValueError: Exception raised if a field has an unexpected type or cannot 1012 be parsed to an expected type. 1013 KeyError: Exception raised if an expected field is not found. 1014 1015 Returns: 1016 Newly parsed record. 1017 """ 1018 year = float(target['year']) 1019 srvy = str(target['srvy']) 1020 survey = str(target['survey']) 1021 survey_id = float(target['survey_id']) 1022 cruise = float(target['cruise']) 1023 haul = float(target['haul']) 1024 stratum = float(target['stratum']) 1025 station = str(target['station']) 1026 vessel_name = str(target['vessel_name']) 1027 vessel_id = float(target['vessel_id']) 1028 date_time = afscgap.convert.convert_to_iso8601(str(target['date_time'])) 1029 latitude_dd = float(target['latitude_dd']) 1030 longitude_dd = float(target['longitude_dd']) 1031 species_code = float(target['species_code']) 1032 common_name = str(target['common_name']) 1033 scientific_name = str(target['scientific_name']) 1034 taxon_confidence = str(target['taxon_confidence']) 1035 cpue_kgha = afscgap.model.get_opt_float(target['cpue_kgha']) 1036 cpue_kgkm2 = afscgap.model.get_opt_float(target['cpue_kgkm2']) 1037 cpue_kg1000km2 = afscgap.model.get_opt_float(target['cpue_kg1000km2']) 1038 cpue_noha = afscgap.model.get_opt_float(target['cpue_noha']) 1039 cpue_nokm2 = afscgap.model.get_opt_float(target['cpue_nokm2']) 1040 cpue_no1000km2 = afscgap.model.get_opt_float(target['cpue_no1000km2']) 1041 weight_kg = afscgap.model.get_opt_float(target['weight_kg']) 1042 count = afscgap.model.get_opt_float(target['count']) 1043 bottom_temperature_c = afscgap.model.get_opt_float( 1044 target['bottom_temperature_c'] 1045 ) 1046 surface_temperature_c = afscgap.model.get_opt_float( 1047 target['surface_temperature_c'] 1048 ) 1049 depth_m = float(target['depth_m']) 1050 distance_fished_km = float(target['distance_fished_km']) 1051 net_width_m = afscgap.model.get_opt_float(target['net_width_m']) 1052 net_height_m = afscgap.model.get_opt_float(target['net_height_m']) 1053 area_swept_ha = float(target['area_swept_ha']) 1054 duration_hr = float(target['duration_hr']) 1055 tsn = afscgap.model.get_opt_int(target['tsn']) 1056 ak_survey_id = int(target['ak_survey_id']) 1057 1058 return ApiRecord( 1059 year, 1060 srvy, 1061 survey, 1062 survey_id, 1063 cruise, 1064 haul, 1065 stratum, 1066 station, 1067 vessel_name, 1068 vessel_id, 1069 date_time, 1070 latitude_dd, 1071 longitude_dd, 1072 species_code, 1073 common_name, 1074 scientific_name, 1075 taxon_confidence, 1076 cpue_kgha, 1077 cpue_kgkm2, 1078 cpue_kg1000km2, 1079 cpue_noha, 1080 cpue_nokm2, 1081 cpue_no1000km2, 1082 weight_kg, 1083 count, 1084 bottom_temperature_c, 1085 surface_temperature_c, 1086 depth_m, 1087 distance_fished_km, 1088 net_width_m, 1089 net_height_m, 1090 area_swept_ha, 1091 duration_hr, 1092 tsn, 1093 ak_survey_id 1094 ) 1095 1096 1097class ParseRecordResult: 1098 """Object with the results of trying to parse a record from the API. 1099 1100 Object with the results of trying to parse a record from the API, allowing 1101 for internal record keeping within the afscgap library. Note that this is 1102 an internal data structure and not expected to reach client code. 1103 """ 1104 1105 def __init__(self, raw_record: dict, 1106 parsed: typing.Optional[afscgap.model.Record]): 1107 """Create a new record of a parse attempt. 1108 1109 Args: 1110 raw_record: Item from the API's JSON response payload that the 1111 library attempted to parse. 1112 parsed: The Record read if successful or None if it could not be 1113 parsed. 1114 """ 1115 self._raw_record = raw_record 1116 self._parsed = parsed 1117 1118 def get_raw_record(self) -> dict: 1119 """Get the input raw JSON record. 1120 1121 Returns: 1122 Item from the API's JSON response payload that the library attempted 1123 to parse. 1124 """ 1125 return self._raw_record 1126 1127 def get_parsed(self) -> typing.Optional[afscgap.model.Record]: 1128 """Get the record that was parsed if successful. 1129 1130 Returns: 1131 The Record read if successful or None if it could not be parsed. 1132 """ 1133 return self._parsed 1134 1135 def meets_requirements(self, allow_incomplete: bool) -> bool: 1136 """Determine if this record is "valid" according to the client code. 1137 1138 Args: 1139 allow_incomplete: Flag indicating if incomplete records are 1140 considered valid. If true, incomplete records will be considered 1141 valid. If false, incomplete records will be considered invalid. 1142 Incomplete means missing any optional fields or failing to 1143 achieve an ISO 8601 date_time value. 1144 1145 Returns: 1146 True if this record was parsed successfully and meets the 1147 requirements specified for being considered "valid" per the 1148 allow_incomplete flag. False otherwise. 1149 """ 1150 if self._parsed is None: 1151 return False 1152 1153 return allow_incomplete or self._parsed.is_complete() 1154 1155 1156def try_parse(target: dict) -> ParseRecordResult: 1157 """Attempt parsing a Record from an input item dictionary from the API. 1158 1159 Params: 1160 target: The dictionary from which to parse. This should be an item 1161 from the items array in the returned JSON payload from the API. 1162 1163 Returns: 1164 Parse result describing if the dictionary was parsed successfully. 1165 """ 1166 try: 1167 return ParseRecordResult(target, parse_record(target)) 1168 except (ValueError, KeyError): 1169 return ParseRecordResult(target, None)
32def build_api_cursor(params: dict, limit: OPT_INT = None, 33 start_offset: OPT_INT = None, filter_incomplete: bool = False, 34 requestor: OPT_REQUESTOR = None, 35 base_url: OPT_STR = None) -> afscgap.cursor.Cursor: 36 """Build a cursor which will iterate over API service results. 37 38 Args: 39 params: Dictionary of filters to apply to the query where a value of 40 None means no filter should be applied on that field. 41 limit: The maximum number of records to return per page. 42 start_offset: The number of records being skipped (number of records 43 prior to query_url). 44 filter_incomplete: Flag indicating if incomplete records should be 45 silently filtered. If true, they will not be returned during 46 iteration and placed in the queue at get_invalid(). If false, 47 they will be returned and those incomplete records' 48 get_complete() will return false. Defaults to false. 49 requestor: Strategy to make HTTP GET requests. If None, will default 50 to requests.get. 51 base_url: The URL at which the API service can be found or None to use 52 a default. Defaults to None. 53 54 Returns: 55 Cursor which iterates over the results from the API service. 56 """ 57 params_safe = copy.deepcopy(params) 58 params_safe['date_time'] = afscgap.convert.convert_from_iso8601( 59 params_safe['date_time'] 60 ) 61 params_ords = afscgap.query_util.interpret_query_to_ords(params_safe) 62 63 query_url = get_query_url(params_ords, base=base_url) 64 65 return ApiServiceCursor( 66 query_url, 67 limit=limit, 68 start_offset=start_offset, 69 requestor=requestor, 70 filter_incomplete=filter_incomplete 71 )
Build a cursor which will iterate over API service results.
Arguments:
- params: Dictionary of filters to apply to the query where a value of None means no filter should be applied on that field.
- limit: The maximum number of records to return per page.
- start_offset: The number of records being skipped (number of records prior to query_url).
- filter_incomplete: Flag indicating if incomplete records should be silently filtered. If true, they will not be returned during iteration and placed in the queue at get_invalid(). If false, they will be returned and those incomplete records' get_complete() will return false. Defaults to false.
- requestor: Strategy to make HTTP GET requests. If None, will default to requests.get.
- base_url: The URL at which the API service can be found or None to use a default. Defaults to None.
Returns:
Cursor which iterates over the results from the API service.
74def get_query_url(params: dict, base: OPT_STR = None) -> str: 75 """Get the URL at which a query can be made. 76 77 Args: 78 params: Dictionary of filters to apply to the query where a value of 79 None means no filter should be applied on that field. 80 base: The URL at which the API service can be found. If None, will use 81 DEFAULT_URL. Defaults to None. 82 83 Returns: 84 URL at which an HTTP GET request can be made to execute the desired 85 query. 86 """ 87 if base is None: 88 base = DEFAULT_URL 89 90 all_items = params.items() 91 items_included = filter(lambda x: x[1] is not None, all_items) 92 included_dict = dict(items_included) 93 included_json = json.dumps(included_dict) 94 95 return '%s?q=%s' % (base, included_json)
Get the URL at which a query can be made.
Arguments:
- params: Dictionary of filters to apply to the query where a value of None means no filter should be applied on that field.
- base: The URL at which the API service can be found. If None, will use DEFAULT_URL. Defaults to None.
Returns:
URL at which an HTTP GET request can be made to execute the desired query.
98class ApiServiceCursor(afscgap.cursor.Cursor): 99 """Object for requests and interpreting API service results.""" 100 101 def __init__(self, query_url: str, limit: OPT_INT = None, 102 start_offset: OPT_INT = None, filter_incomplete: bool = False, 103 requestor: OPT_REQUESTOR = None): 104 """Create a new cursor to manage a request. 105 106 Args: 107 query_url: The URL for the query without pagination information. 108 limit: The maximum number of records to return per page. 109 start_offset: The number of records being skipped (number of records 110 prior to query_url). 111 filter_incomplete: Flag indicating if incomplete records should be 112 silently filtered. If true, they will not be returned during 113 iteration and placed in the queue at get_invalid(). If false, 114 they will be returned and those incomplete records' 115 get_complete() will return false. Defaults to false. 116 requestor: Strategy to make HTTP GET requests. If None, will default 117 to requests.get. 118 """ 119 self._query_url = query_url 120 self._limit = limit 121 self._start_offset = start_offset 122 self._filter_incomplete = filter_incomplete 123 self._queue: queue.Queue[afscgap.model.Record] = queue.Queue() 124 self._invalid_queue: queue.Queue[dict] = queue.Queue() 125 self._done = False 126 127 if requestor: 128 self._request_strategy = requestor 129 else: 130 self._request_strategy = afscgap.http_util.build_requestor() 131 132 self._next_url = self.get_page_url() 133 134 def get_base_url(self) -> str: 135 """Get the URL at which the first page of query results can be found. 136 137 Returns: 138 The URL for the query without pagination information. 139 """ 140 return self._query_url 141 142 def get_limit(self) -> OPT_INT: 143 """Get the page size limit. 144 145 Returns: 146 The maximum number of records to return per page. 147 """ 148 return self._limit 149 150 def get_start_offset(self) -> OPT_INT: 151 """Get the number of inital records to ignore. 152 153 Returns: 154 The number of records being skipped at the start of the result set. 155 """ 156 return self._start_offset 157 158 def get_filtering_incomplete(self) -> bool: 159 """Determine if this cursor is silently filtering incomplete records. 160 161 Returns: 162 Flag indicating if incomplete records should be silently filtered. 163 If true, they will not be returned during iteration and placed in 164 the queue at get_invalid(). If false, they will be returned and 165 those incomplete records' get_complete() will return false. 166 """ 167 return self._filter_incomplete 168 169 def get_page_url(self, offset: OPT_INT = None, 170 limit: OPT_INT = None) -> str: 171 """Get a URL at which a page can be found using this cursor's base url. 172 173 Args: 174 offset: The number of records to skip prior to the page. 175 limit: The maximum number of records to return in the page. 176 Returns: 177 URL at which the requested page can be found. 178 """ 179 180 if offset is None: 181 offset = self._start_offset 182 183 if limit is None: 184 limit = self._limit 185 186 pagination_params = [] 187 188 if offset: 189 pagination_params.append('offset=%d' % offset) 190 191 if limit: 192 pagination_params.append('limit=%d' % limit) 193 194 if len(pagination_params) > 0: 195 pagination_params_str = '&'.join(pagination_params) 196 return self._query_url + '&' + pagination_params_str 197 else: 198 return self._query_url 199 200 def get_page(self, offset: OPT_INT = None, 201 limit: OPT_INT = None, 202 ignore_invalid: bool = False) -> typing.List[afscgap.model.Record]: 203 """Get a page using this cursor's base url. 204 205 Args: 206 offset: The number of records to skip prior to the page. 207 limit: The maximum number of records to return in the page. 208 ignore_invalid: Flag indicating how to handle invalid records. If 209 true, will silently throw away records which could not be 210 parsed. If false, will raise an exception if a record can not 211 be parsed. 212 213 Returns: 214 Results from the page which, regardless of ignore_invalid, may 215 contain a mixture of complete and incomplete records. 216 """ 217 url = self.get_page_url(offset, limit) 218 219 result = self._request_strategy(url) 220 afscgap.http_util.check_result(result) 221 222 result_parsed = result.json() 223 items_raw = result_parsed['items'] 224 225 parsed_maybe = map(try_parse, items_raw) 226 parsed_with_none = map(lambda x: x.get_parsed(), parsed_maybe) 227 228 if ignore_invalid: 229 parsed_no_none = filter(lambda x: x is not None, parsed_with_none) 230 return list(parsed_no_none) # type: ignore 231 else: 232 parsed = list(parsed_with_none) 233 234 if None in parsed: 235 raise RuntimeError('Encountered invalid record.') 236 237 return parsed # type: ignore 238 239 def get_invalid(self) -> 'queue.Queue[dict]': 240 """Get a queue of invalid / incomplete records found so far. 241 242 Returns: 243 Queue with dictionaries containing the raw data returned from the 244 API that did not have valid values for all required fields. Note 245 that this will include incomplete records as well if 246 get_filtering_incomplete() is true and will not contain incomplete 247 records otherwise. 248 """ 249 return self._invalid_queue 250 251 def to_dicts(self) -> typing.Iterable[dict]: 252 """Create an iterator which converts Records to dicts. 253 254 Returns: 255 Iterator which returns dictionaries instead of Record objects but 256 has otherwise the same beahavior as iterating in this Cursor 257 directly. 258 """ 259 return map(lambda x: x.to_dict(), self) 260 261 def get_next(self) -> typing.Optional[afscgap.model.Record]: 262 """Get the next value for this Cursor. 263 264 Returns: 265 The next value waiting if cached in the cursor's results queue or 266 as just retrieved from a new page gathered by HTTP request. Will 267 return None if no remain. 268 """ 269 self._load_next_page() 270 271 if self._queue.empty(): 272 return None 273 else: 274 return self._queue.get() 275 276 def _load_next_page(self): 277 """Request and parse additional results if they exist. 278 279 Request and parse the next page(s) of results if one exists, putting it 280 into the waiting results queues. Note that this will contiune to 281 request new pages until finding valid results or no results remain. 282 """ 283 while self._queue.empty() and not self._done: 284 self._queue_next_page() 285 286 def _queue_next_page(self): 287 """Request the next page of waiting results. 288 289 Request the next page of waiting results, putting newly returned data 290 into the waiting queues and updating the next url / done internal 291 state in the process. 292 """ 293 if self._done: 294 return 295 296 result = self._request_strategy(self._next_url) 297 afscgap.http_util.check_result(result) 298 299 result_parsed = result.json() 300 301 items_raw = result_parsed['items'] 302 303 items_parsed = map(try_parse, items_raw) 304 305 # If we are filtering incomplete records, we will not allow incomplete. 306 allow_incomplete = not self._filter_incomplete 307 308 for parse_result in items_parsed: 309 if parse_result.meets_requirements(allow_incomplete): 310 self._queue.put(parse_result.get_parsed()) 311 else: 312 self._invalid_queue.put(parse_result.get_raw_record()) 313 314 next_url = self._find_next_url(result_parsed) 315 self._done = next_url is None 316 self._next_url = next_url 317 318 def _find_next_url(self, target: dict) -> OPT_STR: 319 """Look for the URL with the next page of results if it exists. 320 321 Args: 322 target: The raw complete parsed JSON response from the API. 323 324 Returns: 325 The URL where the next page of results can be found via HTTP GET 326 request or None if target indicates that no results remain. 327 """ 328 if not target['hasMore']: 329 return None 330 331 links = target['links'] 332 matching = filter(lambda x: x['rel'] == 'next', links) 333 matching_hrefs = map(lambda x: x['href'], matching) 334 hrefs_realized = list(matching_hrefs) 335 336 if len(hrefs_realized) == 0: 337 raise RuntimeError('Could not find next URL but hasMore was true.') 338 339 return hrefs_realized[0]
Object for requests and interpreting API service results.
101 def __init__(self, query_url: str, limit: OPT_INT = None, 102 start_offset: OPT_INT = None, filter_incomplete: bool = False, 103 requestor: OPT_REQUESTOR = None): 104 """Create a new cursor to manage a request. 105 106 Args: 107 query_url: The URL for the query without pagination information. 108 limit: The maximum number of records to return per page. 109 start_offset: The number of records being skipped (number of records 110 prior to query_url). 111 filter_incomplete: Flag indicating if incomplete records should be 112 silently filtered. If true, they will not be returned during 113 iteration and placed in the queue at get_invalid(). If false, 114 they will be returned and those incomplete records' 115 get_complete() will return false. Defaults to false. 116 requestor: Strategy to make HTTP GET requests. If None, will default 117 to requests.get. 118 """ 119 self._query_url = query_url 120 self._limit = limit 121 self._start_offset = start_offset 122 self._filter_incomplete = filter_incomplete 123 self._queue: queue.Queue[afscgap.model.Record] = queue.Queue() 124 self._invalid_queue: queue.Queue[dict] = queue.Queue() 125 self._done = False 126 127 if requestor: 128 self._request_strategy = requestor 129 else: 130 self._request_strategy = afscgap.http_util.build_requestor() 131 132 self._next_url = self.get_page_url()
Create a new cursor to manage a request.
Arguments:
- query_url: The URL for the query without pagination information.
- limit: The maximum number of records to return per page.
- start_offset: The number of records being skipped (number of records prior to query_url).
- filter_incomplete: Flag indicating if incomplete records should be silently filtered. If true, they will not be returned during iteration and placed in the queue at get_invalid(). If false, they will be returned and those incomplete records' get_complete() will return false. Defaults to false.
- requestor: Strategy to make HTTP GET requests. If None, will default to requests.get.
134 def get_base_url(self) -> str: 135 """Get the URL at which the first page of query results can be found. 136 137 Returns: 138 The URL for the query without pagination information. 139 """ 140 return self._query_url
Get the URL at which the first page of query results can be found.
Returns:
The URL for the query without pagination information.
142 def get_limit(self) -> OPT_INT: 143 """Get the page size limit. 144 145 Returns: 146 The maximum number of records to return per page. 147 """ 148 return self._limit
Get the page size limit.
Returns:
The maximum number of records to return per page.
150 def get_start_offset(self) -> OPT_INT: 151 """Get the number of inital records to ignore. 152 153 Returns: 154 The number of records being skipped at the start of the result set. 155 """ 156 return self._start_offset
Get the number of inital records to ignore.
Returns:
The number of records being skipped at the start of the result set.
158 def get_filtering_incomplete(self) -> bool: 159 """Determine if this cursor is silently filtering incomplete records. 160 161 Returns: 162 Flag indicating if incomplete records should be silently filtered. 163 If true, they will not be returned during iteration and placed in 164 the queue at get_invalid(). If false, they will be returned and 165 those incomplete records' get_complete() will return false. 166 """ 167 return self._filter_incomplete
Determine if this cursor is silently filtering incomplete records.
Returns:
Flag indicating if incomplete records should be silently filtered. If true, they will not be returned during iteration and placed in the queue at get_invalid(). If false, they will be returned and those incomplete records' get_complete() will return false.
169 def get_page_url(self, offset: OPT_INT = None, 170 limit: OPT_INT = None) -> str: 171 """Get a URL at which a page can be found using this cursor's base url. 172 173 Args: 174 offset: The number of records to skip prior to the page. 175 limit: The maximum number of records to return in the page. 176 Returns: 177 URL at which the requested page can be found. 178 """ 179 180 if offset is None: 181 offset = self._start_offset 182 183 if limit is None: 184 limit = self._limit 185 186 pagination_params = [] 187 188 if offset: 189 pagination_params.append('offset=%d' % offset) 190 191 if limit: 192 pagination_params.append('limit=%d' % limit) 193 194 if len(pagination_params) > 0: 195 pagination_params_str = '&'.join(pagination_params) 196 return self._query_url + '&' + pagination_params_str 197 else: 198 return self._query_url
Get a URL at which a page can be found using this cursor's base url.
Arguments:
- offset: The number of records to skip prior to the page.
- limit: The maximum number of records to return in the page.
Returns:
URL at which the requested page can be found.
200 def get_page(self, offset: OPT_INT = None, 201 limit: OPT_INT = None, 202 ignore_invalid: bool = False) -> typing.List[afscgap.model.Record]: 203 """Get a page using this cursor's base url. 204 205 Args: 206 offset: The number of records to skip prior to the page. 207 limit: The maximum number of records to return in the page. 208 ignore_invalid: Flag indicating how to handle invalid records. If 209 true, will silently throw away records which could not be 210 parsed. If false, will raise an exception if a record can not 211 be parsed. 212 213 Returns: 214 Results from the page which, regardless of ignore_invalid, may 215 contain a mixture of complete and incomplete records. 216 """ 217 url = self.get_page_url(offset, limit) 218 219 result = self._request_strategy(url) 220 afscgap.http_util.check_result(result) 221 222 result_parsed = result.json() 223 items_raw = result_parsed['items'] 224 225 parsed_maybe = map(try_parse, items_raw) 226 parsed_with_none = map(lambda x: x.get_parsed(), parsed_maybe) 227 228 if ignore_invalid: 229 parsed_no_none = filter(lambda x: x is not None, parsed_with_none) 230 return list(parsed_no_none) # type: ignore 231 else: 232 parsed = list(parsed_with_none) 233 234 if None in parsed: 235 raise RuntimeError('Encountered invalid record.') 236 237 return parsed # type: ignore
Get a page using this cursor's base url.
Arguments:
- offset: The number of records to skip prior to the page.
- limit: The maximum number of records to return in the page.
- ignore_invalid: Flag indicating how to handle invalid records. If true, will silently throw away records which could not be parsed. If false, will raise an exception if a record can not be parsed.
Returns:
Results from the page which, regardless of ignore_invalid, may contain a mixture of complete and incomplete records.
239 def get_invalid(self) -> 'queue.Queue[dict]': 240 """Get a queue of invalid / incomplete records found so far. 241 242 Returns: 243 Queue with dictionaries containing the raw data returned from the 244 API that did not have valid values for all required fields. Note 245 that this will include incomplete records as well if 246 get_filtering_incomplete() is true and will not contain incomplete 247 records otherwise. 248 """ 249 return self._invalid_queue
Get a queue of invalid / incomplete records found so far.
Returns:
Queue with dictionaries containing the raw data returned from the API that did not have valid values for all required fields. Note that this will include incomplete records as well if get_filtering_incomplete() is true and will not contain incomplete records otherwise.
251 def to_dicts(self) -> typing.Iterable[dict]: 252 """Create an iterator which converts Records to dicts. 253 254 Returns: 255 Iterator which returns dictionaries instead of Record objects but 256 has otherwise the same beahavior as iterating in this Cursor 257 directly. 258 """ 259 return map(lambda x: x.to_dict(), self)
Create an iterator which converts Records to dicts.
Returns:
Iterator which returns dictionaries instead of Record objects but has otherwise the same beahavior as iterating in this Cursor directly.
261 def get_next(self) -> typing.Optional[afscgap.model.Record]: 262 """Get the next value for this Cursor. 263 264 Returns: 265 The next value waiting if cached in the cursor's results queue or 266 as just retrieved from a new page gathered by HTTP request. Will 267 return None if no remain. 268 """ 269 self._load_next_page() 270 271 if self._queue.empty(): 272 return None 273 else: 274 return self._queue.get()
Get the next value for this Cursor.
Returns:
The next value waiting if cached in the cursor's results queue or as just retrieved from a new page gathered by HTTP request. Will return None if no remain.
342class ApiRecord(afscgap.model.Record): 343 """Data structure describing a single record returned by the API.""" 344 345 def __init__(self, year: float, srvy: str, survey: str, survey_id: float, 346 cruise: float, haul: float, stratum: float, station: str, 347 vessel_name: str, vessel_id: float, date_time: str, latitude_dd: float, 348 longitude_dd: float, species_code: float, common_name: str, 349 scientific_name: str, taxon_confidence: str, cpue_kgha: OPT_FLOAT, 350 cpue_kgkm2: OPT_FLOAT, cpue_kg1000km2: OPT_FLOAT, cpue_noha: OPT_FLOAT, 351 cpue_nokm2: OPT_FLOAT, cpue_no1000km2: OPT_FLOAT, weight_kg: OPT_FLOAT, 352 count: OPT_FLOAT, bottom_temperature_c: OPT_FLOAT, 353 surface_temperature_c: OPT_FLOAT, depth_m: float, 354 distance_fished_km: float, net_width_m: OPT_FLOAT, 355 net_height_m: OPT_FLOAT, area_swept_ha: float, duration_hr: float, 356 tsn: OPT_INT, ak_survey_id: int): 357 """Create a new record. 358 359 Args: 360 year: Year for the survey in which this observation was made. 361 srvy: The name of the survey in which this observation was made. 362 NBS (N Bearing Sea), EBS (SE Bearing Sea), BSS (Bearing Sea 363 Slope), or GOA (Gulf of Alaska) 364 survey: Long form description of the survey in which the observation 365 was made. 366 survey_id: Unique numeric ID for the survey. 367 cruise: An ID uniquely identifying the cruise in which the 368 observation was made. Multiple cruises in a survey. 369 haul: An ID uniquely identifying the haul in which this observation 370 was made. Multiple hauls per cruises. 371 stratum: Unique ID for statistical area / survey combination as 372 described in the metadata or 0 if an experimental tow. 373 station: Station associated with the survey. 374 vessel_name: Unique ID describing the vessel that made this 375 observation. This is left as a string but, in practice, is 376 likely numeric. 377 vessel_id: Name of the vessel at the time the observation was made 378 with multiple names potentially associated with a vessel ID. 379 date_time: The date and time of the haul which has been attempted to 380 be transformed to an ISO 8601 string without timezone info. 381 If it couldn’t be transformed, the original string. 382 latitude_dd: Latitude in decimal degrees associated with the haul. 383 longitude_dd: Longitude in decimal degrees associated with the haul. 384 species_code: Unique ID associated with the species observed. 385 common_name: The “common name” associated with the species observed. 386 scientific_name: The “scientific name” associated with the species 387 observed. 388 taxon_confidence: Confidence flag regarding ability to identify 389 species (High, Moderate, Low). In practice, this can also be 390 Unassessed. 391 cpue_kgha: Catch weight divided by net area (kg / hectares) if 392 available. None if could not interpret as a float. 393 cpue_kgkm2: Catch weight divided by net area (kg / km^2) if 394 available. None if could not interpret as a float. 395 cpue_kg1000km2: Catch weight divided by net area (kg / km^2 * 1000) 396 if available. See metadata. None if could not interpret as a 397 float. 398 cpue_noha: Catch number divided by net sweep area if available 399 (count / hectares). None if could not interpret as a float. 400 cpue_nokm2: Catch number divided by net sweep area if available 401 (count / km^2). See metadata. None if could not interpret as a 402 float. 403 cpue_no1000km2: Catch number divided by net sweep area if available 404 (count / km^2 * 1000). See metadata. None if could not interpret 405 as a float. 406 weight_kg: Taxon weight (kg) if available. See metadata. None if 407 could not interpret as a float. 408 count: Total number of organism individuals in haul. None if could 409 not interpret as a float. 410 bottom_temperature_c: Bottom temperature associated with observation 411 if available in Celsius. None if not given or could not 412 interpret as a float. 413 surface_temperature_c: Surface temperature associated with 414 observation if available in Celsius. None if not given or could 415 not interpret as a float. 416 depth_m: Depth of the bottom in meters. 417 distance_fished_km: Distance of the net fished as km. 418 net_width_m: Distance of the net fished as m. 419 net_height_m: Height of the net fished as m. 420 area_swept_ha: Area covered by the net while fishing in hectares. 421 duration_hr: Duration of the haul as number of hours. 422 tsn: Taxonomic information system species code. 423 ak_survey_id: AK identifier for the survey. 424 """ 425 self._year = year 426 self._srvy = srvy 427 self._survey = survey 428 self._survey_id = survey_id 429 self._cruise = cruise 430 self._haul = haul 431 self._stratum = stratum 432 self._station = station 433 self._vessel_name = vessel_name 434 self._vessel_id = vessel_id 435 self._date_time = date_time 436 self._latitude_dd = latitude_dd 437 self._longitude_dd = longitude_dd 438 self._species_code = species_code 439 self._common_name = common_name 440 self._scientific_name = scientific_name 441 self._taxon_confidence = taxon_confidence 442 self._cpue_kgha = cpue_kgha 443 self._cpue_kgkm2 = cpue_kgkm2 444 self._cpue_kg1000km2 = cpue_kg1000km2 445 self._cpue_noha = cpue_noha 446 self._cpue_nokm2 = cpue_nokm2 447 self._cpue_no1000km2 = cpue_no1000km2 448 self._weight_kg = weight_kg 449 self._count = count 450 self._bottom_temperature_c = bottom_temperature_c 451 self._surface_temperature_c = surface_temperature_c 452 self._depth_m = depth_m 453 self._distance_fished_km = distance_fished_km 454 self._net_width_m = net_width_m 455 self._net_height_m = net_height_m 456 self._area_swept_ha = area_swept_ha 457 self._duration_hr = duration_hr 458 self._tsn = tsn 459 self._ak_survey_id = ak_survey_id 460 461 def get_year(self) -> float: 462 """Get the field labeled as year in the API. 463 464 Returns: 465 Year for the survey in which this observation was made. 466 """ 467 return self._year 468 469 def get_srvy(self) -> str: 470 """Get the field labeled as srvy in the API. 471 472 Returns: 473 The name of the survey in which this observation was made. NBS (N 474 Bearing Sea), EBS (SE Bearing Sea), BSS (Bearing Sea Slope), or GOA 475 (Gulf of Alaska) 476 """ 477 return self._srvy 478 479 def get_survey(self) -> str: 480 """Get the field labeled as survey in the API. 481 482 Returns: 483 Long form description of the survey in which the observation was 484 made. 485 """ 486 return self._survey 487 488 def get_survey_id(self) -> float: 489 """Get the field labeled as survey_id in the API. 490 491 Returns: 492 Unique numeric ID for the survey. 493 """ 494 return self._survey_id 495 496 def get_cruise(self) -> float: 497 """Get the field labeled as cruise in the API. 498 499 Returns: 500 An ID uniquely identifying the cruise in which the observation was 501 made. Multiple cruises in a survey. 502 """ 503 return self._cruise 504 505 def get_haul(self) -> float: 506 """Get the field labeled as haul in the API. 507 508 Returns: 509 An ID uniquely identifying the haul in which this observation was 510 made. Multiple hauls per cruises. 511 """ 512 return self._haul 513 514 def get_stratum(self) -> float: 515 """Get the field labeled as stratum in the API. 516 517 Returns: 518 Unique ID for statistical area / survey combination as described in 519 the metadata or 0 if an experimental tow. 520 """ 521 return self._stratum 522 523 def get_station(self) -> str: 524 """Get the field labeled as station in the API. 525 526 Returns: 527 Station associated with the survey. 528 """ 529 return self._station 530 531 def get_vessel_name(self) -> str: 532 """Get the field labeled as vessel_name in the API. 533 534 Returns: 535 Unique ID describing the vessel that made this observation. This is 536 left as a string but, in practice, is likely numeric. 537 """ 538 return self._vessel_name 539 540 def get_vessel_id(self) -> float: 541 """Get the field labeled as vessel_id in the API. 542 543 Returns: 544 Name of the vessel at the time the observation was made with 545 multiple names potentially associated with a vessel ID. 546 """ 547 return self._vessel_id 548 549 def get_date_time(self) -> str: 550 """Get the field labeled as date_time in the API. 551 552 Returns: 553 The date and time of the haul which has been attempted to be 554 transformed to an ISO 8601 string without timezone info. If it 555 couldn’t be transformed, the original string is reported. 556 """ 557 return self._date_time 558 559 def get_latitude(self, units: str = 'dd') -> float: 560 """Get the field labeled as latitude_dd in the API. 561 562 Args: 563 units: The units to return this value in. Only supported is dd for 564 degrees. Deafults to dd. 565 566 Returns: 567 Latitude in decimal degrees associated with the haul. 568 """ 569 return afscgap.model.assert_float_present( 570 afscgap.convert.convert_degrees(self._latitude_dd, units) 571 ) 572 573 def get_longitude(self, units: str = 'dd') -> float: 574 """Get the field labeled as longitude_dd in the API. 575 576 Args: 577 units: The units to return this value in. Only supported is dd for 578 degrees. Deafults to dd. 579 580 Returns: 581 Longitude in decimal degrees associated with the haul. 582 """ 583 return afscgap.model.assert_float_present( 584 afscgap.convert.convert_degrees(self._longitude_dd, units) 585 ) 586 587 def get_species_code(self) -> float: 588 """Get the field labeled as species_code in the API. 589 590 Returns: 591 Unique ID associated with the species observed. 592 """ 593 return self._species_code 594 595 def get_common_name(self) -> str: 596 """Get the field labeled as common_name in the API. 597 598 Returns: 599 The “common name” associated with the species observed. Example: 600 Pacific glass shrimp. 601 """ 602 return self._common_name 603 604 def get_scientific_name(self) -> str: 605 """Get the field labeled as scientific_name in the API. 606 607 Returns: 608 The “scientific name” associated with the species observed. Example: 609 Pasiphaea pacifica. 610 """ 611 return self._scientific_name 612 613 def get_taxon_confidence(self) -> str: 614 """Get the field labeled as taxon_confidence in the API. 615 616 Returns: 617 Confidence flag regarding ability to identify species (High, 618 Moderate, Low). In practice, this can also be Unassessed. 619 """ 620 return self._taxon_confidence 621 622 def get_cpue_weight_maybe(self, units: str = 'kg/ha') -> OPT_FLOAT: 623 """Get a field labeled as cpue_* in the API. 624 625 Args: 626 units: The desired units for the catch per unit effort. Options: 627 kg/ha, kg/km2, kg1000/km2. Defaults to kg/ha. 628 629 Returns: 630 Catch weight divided by net area (in given units) if available. See 631 metadata. None if could not interpret as a float. If an inferred 632 zero catch record, will be zero. 633 """ 634 return { 635 'kg/ha': self._cpue_kgha, 636 'kg/km2': self._cpue_kgkm2, 637 'kg1000/km2': self._cpue_kg1000km2 638 }[units] 639 640 def get_cpue_count_maybe(self, units: str = 'kg/ha') -> OPT_FLOAT: 641 """Get the field labeled as cpue_* in the API. 642 643 Get the catch per unit effort from the record with one of the following 644 units: kg/ha, kg/km2, kg1000/km2. 645 646 Args: 647 units: The desired units for the catch per unit effort. Options: 648 count/ha, count/km2, and count1000/km2. Defaults to count/ha. 649 650 Returns: 651 Catch weight divided by net area (in given units) if available. See 652 metadata. None if could not interpret as a float. If an inferred 653 zero catch record, will be zero. 654 """ 655 return { 656 'count/ha': self._cpue_noha, 657 'count/km2': self._cpue_nokm2, 658 'count1000/km2': self._cpue_no1000km2 659 }[units] 660 661 def get_weight_maybe(self, units: str = 'kg') -> OPT_FLOAT: 662 """Get the field labeled as weight_kg in the API. 663 664 Args: 665 units: The units in which the weight should be returned. Options are 666 g, kg for grams and kilograms respectively. Deafults to kg. 667 668 Returns: 669 Taxon weight if available. See metadata. None if could not 670 interpret as a float. If an inferred zero catch record, will be 671 zero. 672 """ 673 return afscgap.convert.convert_weight(self._weight_kg, units) 674 675 def get_count_maybe(self) -> OPT_FLOAT: 676 """Get the field labeled as count in the API. 677 678 Returns: 679 Total number of organism individuals in haul. None if could not 680 interpret as a float. 681 """ 682 return self._count 683 684 def get_bottom_temperature_maybe(self, units: str = 'c') -> OPT_FLOAT: 685 """Get the field labeled as bottom_temperature_c in the API. 686 687 Args: 688 units: The units in which the temperature should be returned. 689 Options: c or f for Celcius and Fahrenheit respectively. 690 Defaults to c. 691 692 Returns: 693 Bottom temperature associated with observation / inferrence if 694 available in desired units. None if not given or could not interpret 695 as a float. 696 """ 697 return afscgap.convert.convert_temperature( 698 self._bottom_temperature_c, 699 units 700 ) 701 702 def get_surface_temperature_maybe(self, units: str = 'c') -> OPT_FLOAT: 703 """Get the field labeled as surface_temperature_c in the API. 704 705 Args: 706 units: The units in which the temperature should be returned. 707 Options: c or f for Celcius and Fahrenheit respectively. 708 Defaults to c. 709 710 Returns: 711 Surface temperature associated with observation / inferrence if 712 available. None if not given or could not interpret as a float. 713 """ 714 return afscgap.convert.convert_temperature( 715 self._surface_temperature_c, 716 units 717 ) 718 719 def get_depth(self, units: str = 'm') -> float: 720 """Get the field labeled as depth_m in the API. 721 722 Args: 723 units: The units in which the distance should be returned. Options: 724 m or km for meters and kilometers respectively. Defaults to m. 725 726 Returns: 727 Depth of the bottom. 728 """ 729 return afscgap.model.assert_float_present( 730 afscgap.convert.convert_distance(self._depth_m, units) 731 ) 732 733 def get_distance_fished(self, units: str = 'm') -> float: 734 """Get the field labeled as distance_fished_km in the API. 735 736 Args: 737 units: The units in which the distance should be returned. Options: 738 m or km for meters and kilometers respectively. Defaults to m. 739 740 Returns: 741 Distance of the net fished. 742 """ 743 return afscgap.model.assert_float_present( 744 afscgap.convert.convert_distance( 745 self._distance_fished_km * 1000, 746 units 747 ) 748 ) 749 750 def get_net_width_maybe(self, units: str = 'm') -> OPT_FLOAT: 751 """Get the field labeled as net_width_m in the API. 752 753 Args: 754 units: The units in which the distance should be returned. Options: 755 m or km for meters and kilometers respectively. Defaults to m. 756 757 Returns: 758 Distance of the net fished or None if not given. 759 """ 760 return afscgap.convert.convert_distance( 761 self._net_width_m, 762 units 763 ) 764 765 def get_net_height_maybe(self, units: str = 'm') -> OPT_FLOAT: 766 """Get the field labeled as net_height_m in the API. 767 768 Args: 769 units: The units in which the distance should be returned. Options: 770 m or km for meters and kilometers respectively. Defaults to m. 771 772 Returns: 773 Height of the net fished or None if not given. 774 """ 775 return afscgap.convert.convert_distance( 776 self._net_height_m, 777 units 778 ) 779 780 def get_net_width(self, units: str = 'm') -> float: 781 """Get the field labeled as net_width_m in the API. 782 783 Args: 784 units: The units in which the distance should be returned. Options: 785 m or km for meters and kilometers respectively. Defaults to m. 786 787 Returns: 788 Distance of the net fished after asserting it is given. 789 """ 790 return afscgap.model.assert_float_present( 791 self.get_net_width_maybe(units=units) 792 ) 793 794 def get_net_height(self, units: str = 'm') -> float: 795 """Get the field labeled as net_height_m in the API. 796 797 Args: 798 units: The units in which the distance should be returned. Options: 799 m or km for meters and kilometers respectively. Defaults to m. 800 801 Returns: 802 Height of the net fished after asserting it is given. 803 """ 804 return afscgap.model.assert_float_present( 805 self.get_net_height_maybe(units=units) 806 ) 807 808 def get_area_swept(self, units: str = 'ha') -> float: 809 """Get the field labeled as area_swept_ha in the API. 810 811 Args: 812 units: The units in which the area should be returned. Options: 813 ha, m2, km2. Defaults to ha. 814 815 Returns: 816 Area covered by the net while fishing in desired units. 817 """ 818 return afscgap.model.assert_float_present( 819 afscgap.convert.convert_area(self._area_swept_ha, units) 820 ) 821 822 def get_duration(self, units: str = 'hr') -> float: 823 """Get the field labeled as duration_hr in the API. 824 825 Args: 826 units: The units in which the duration should be returned. Options: 827 day, hr, min. Defaults to hr. 828 829 Returns: 830 Duration of the haul. 831 """ 832 return afscgap.model.assert_float_present( 833 afscgap.convert.convert_time(self._duration_hr, units) 834 ) 835 836 def get_tsn(self) -> int: 837 """Get the field labeled as tsn in the API. 838 839 Returns: 840 Taxonomic information system species code. 841 """ 842 return afscgap.model.assert_int_present(self._tsn) 843 844 def get_tsn_maybe(self) -> OPT_INT: 845 """Get the field labeled as tsn in the API or None. 846 847 Returns: 848 Taxonomic information system species code if it could be parsed as 849 an int and None otherwise. 850 """ 851 return self._tsn 852 853 def get_ak_survey_id(self) -> int: 854 """Get the field labeled as ak_survey_id in the API. 855 856 Returns: 857 AK identifier for the survey. 858 """ 859 return self._ak_survey_id 860 861 def get_ak_survey_id_maybe(self) -> OPT_INT: 862 """Get the field labeled as ak_survey_id in the API. 863 864 Returns: 865 AK identifier for the survey or None if not given. 866 """ 867 return self._ak_survey_id 868 869 def get_cpue_weight(self, units: str = 'kg/ha') -> float: 870 """Get the value of field cpue_kgha with validity assert. 871 872 Args: 873 units: The desired units for the catch per unit effort. Options: 874 kg/ha, kg/km2, kg1000/km2. Defaults to kg/ha. 875 876 Raises: 877 AssertionError: Raised if this field was not given by the API or 878 could not be parsed as expected. 879 880 Returns: 881 Catch weight divided by net area (kg / hectares) if available. See 882 metadata. 883 """ 884 return afscgap.model.assert_float_present( 885 self.get_cpue_weight_maybe(units=units) 886 ) 887 888 def get_cpue_count(self, units: str = 'count/ha') -> float: 889 """Get the value of field cpue_noha with validity assert. 890 891 Args: 892 units: The desired units for the catch per unit effort. Options: 893 count/ha, count/km2, and count1000/km2. Defaults to count/ha. 894 895 Raises: 896 AssertionError: Raised if this field was not given by the API or 897 could not be parsed as expected. 898 899 Returns: 900 Catch number divided by net sweep area if available (count / 901 hectares). See metadata. 902 """ 903 return afscgap.model.assert_float_present( 904 self.get_cpue_count_maybe(units=units) 905 ) 906 907 def get_weight(self, units: str = 'kg') -> float: 908 """Get the value of field weight_kg with validity assert. 909 910 Args: 911 units: The units in which the weight should be returned. Options are 912 g, kg for grams and kilograms respectively. Deafults to kg. 913 914 Raises: 915 AssertionError: Raised if this field was not given by the API or 916 could not be parsed as expected. 917 918 Returns: 919 Taxon weight (kg) if available. See metadata. 920 """ 921 return afscgap.model.assert_float_present( 922 self.get_weight_maybe(units=units) 923 ) 924 925 def get_count(self) -> float: 926 """Get the value of field count with validity assert. 927 928 Raises: 929 AssertionError: Raised if this field was not given by the API or 930 could not be parsed as expected. 931 932 Returns: 933 Total number of organism individuals in haul. 934 """ 935 return afscgap.model.assert_float_present(self._count) 936 937 def get_bottom_temperature(self, units='c') -> float: 938 """Get the value of field bottom_temperature_c with validity assert. 939 940 Args: 941 units: The units in which the temperature should be returned. 942 Options: c or f for Celcius and Fahrenheit respectively. 943 Defaults to c. 944 945 Raises: 946 AssertionError: Raised if this field was not given by the API or 947 could not be parsed as expected. 948 949 Returns: 950 Bottom temperature associated with observation / inferrence if 951 available. 952 """ 953 return afscgap.model.assert_float_present( 954 self.get_bottom_temperature_maybe(units=units) 955 ) 956 957 def get_surface_temperature(self, units='c') -> float: 958 """Get the value of field surface_temperature_c with validity assert. 959 960 Args: 961 units: The units in which the temperature should be returned. 962 Options: c or f for Celcius and Fahrenheit respectively. 963 Defaults to c. 964 965 Raises: 966 AssertionError: Raised if this field was not given by the API or 967 could not be parsed as expected. 968 969 Returns: 970 Surface temperature associated with observation / inferrence if 971 available. 972 """ 973 return afscgap.model.assert_float_present( 974 self.get_surface_temperature_maybe(units=units) 975 ) 976 977 def is_complete(self) -> bool: 978 """Determine if this record has all of its values filled in. 979 980 Returns: 981 True if all optional fields have a parsed value with the expected 982 type and false otherwise. 983 """ 984 optional_fields = [ 985 self._cpue_kgha, 986 self._cpue_kgkm2, 987 self._cpue_kg1000km2, 988 self._cpue_noha, 989 self._cpue_nokm2, 990 self._cpue_no1000km2, 991 self._weight_kg, 992 self._count, 993 self._bottom_temperature_c, 994 self._surface_temperature_c, 995 self._tsn 996 ] 997 998 has_none = None in optional_fields 999 all_fields_present = not has_none 1000 has_valid_date_time = afscgap.convert.is_iso8601(self._date_time) 1001 1002 return all_fields_present and has_valid_date_time
Data structure describing a single record returned by the API.
345 def __init__(self, year: float, srvy: str, survey: str, survey_id: float, 346 cruise: float, haul: float, stratum: float, station: str, 347 vessel_name: str, vessel_id: float, date_time: str, latitude_dd: float, 348 longitude_dd: float, species_code: float, common_name: str, 349 scientific_name: str, taxon_confidence: str, cpue_kgha: OPT_FLOAT, 350 cpue_kgkm2: OPT_FLOAT, cpue_kg1000km2: OPT_FLOAT, cpue_noha: OPT_FLOAT, 351 cpue_nokm2: OPT_FLOAT, cpue_no1000km2: OPT_FLOAT, weight_kg: OPT_FLOAT, 352 count: OPT_FLOAT, bottom_temperature_c: OPT_FLOAT, 353 surface_temperature_c: OPT_FLOAT, depth_m: float, 354 distance_fished_km: float, net_width_m: OPT_FLOAT, 355 net_height_m: OPT_FLOAT, area_swept_ha: float, duration_hr: float, 356 tsn: OPT_INT, ak_survey_id: int): 357 """Create a new record. 358 359 Args: 360 year: Year for the survey in which this observation was made. 361 srvy: The name of the survey in which this observation was made. 362 NBS (N Bearing Sea), EBS (SE Bearing Sea), BSS (Bearing Sea 363 Slope), or GOA (Gulf of Alaska) 364 survey: Long form description of the survey in which the observation 365 was made. 366 survey_id: Unique numeric ID for the survey. 367 cruise: An ID uniquely identifying the cruise in which the 368 observation was made. Multiple cruises in a survey. 369 haul: An ID uniquely identifying the haul in which this observation 370 was made. Multiple hauls per cruises. 371 stratum: Unique ID for statistical area / survey combination as 372 described in the metadata or 0 if an experimental tow. 373 station: Station associated with the survey. 374 vessel_name: Unique ID describing the vessel that made this 375 observation. This is left as a string but, in practice, is 376 likely numeric. 377 vessel_id: Name of the vessel at the time the observation was made 378 with multiple names potentially associated with a vessel ID. 379 date_time: The date and time of the haul which has been attempted to 380 be transformed to an ISO 8601 string without timezone info. 381 If it couldn’t be transformed, the original string. 382 latitude_dd: Latitude in decimal degrees associated with the haul. 383 longitude_dd: Longitude in decimal degrees associated with the haul. 384 species_code: Unique ID associated with the species observed. 385 common_name: The “common name” associated with the species observed. 386 scientific_name: The “scientific name” associated with the species 387 observed. 388 taxon_confidence: Confidence flag regarding ability to identify 389 species (High, Moderate, Low). In practice, this can also be 390 Unassessed. 391 cpue_kgha: Catch weight divided by net area (kg / hectares) if 392 available. None if could not interpret as a float. 393 cpue_kgkm2: Catch weight divided by net area (kg / km^2) if 394 available. None if could not interpret as a float. 395 cpue_kg1000km2: Catch weight divided by net area (kg / km^2 * 1000) 396 if available. See metadata. None if could not interpret as a 397 float. 398 cpue_noha: Catch number divided by net sweep area if available 399 (count / hectares). None if could not interpret as a float. 400 cpue_nokm2: Catch number divided by net sweep area if available 401 (count / km^2). See metadata. None if could not interpret as a 402 float. 403 cpue_no1000km2: Catch number divided by net sweep area if available 404 (count / km^2 * 1000). See metadata. None if could not interpret 405 as a float. 406 weight_kg: Taxon weight (kg) if available. See metadata. None if 407 could not interpret as a float. 408 count: Total number of organism individuals in haul. None if could 409 not interpret as a float. 410 bottom_temperature_c: Bottom temperature associated with observation 411 if available in Celsius. None if not given or could not 412 interpret as a float. 413 surface_temperature_c: Surface temperature associated with 414 observation if available in Celsius. None if not given or could 415 not interpret as a float. 416 depth_m: Depth of the bottom in meters. 417 distance_fished_km: Distance of the net fished as km. 418 net_width_m: Distance of the net fished as m. 419 net_height_m: Height of the net fished as m. 420 area_swept_ha: Area covered by the net while fishing in hectares. 421 duration_hr: Duration of the haul as number of hours. 422 tsn: Taxonomic information system species code. 423 ak_survey_id: AK identifier for the survey. 424 """ 425 self._year = year 426 self._srvy = srvy 427 self._survey = survey 428 self._survey_id = survey_id 429 self._cruise = cruise 430 self._haul = haul 431 self._stratum = stratum 432 self._station = station 433 self._vessel_name = vessel_name 434 self._vessel_id = vessel_id 435 self._date_time = date_time 436 self._latitude_dd = latitude_dd 437 self._longitude_dd = longitude_dd 438 self._species_code = species_code 439 self._common_name = common_name 440 self._scientific_name = scientific_name 441 self._taxon_confidence = taxon_confidence 442 self._cpue_kgha = cpue_kgha 443 self._cpue_kgkm2 = cpue_kgkm2 444 self._cpue_kg1000km2 = cpue_kg1000km2 445 self._cpue_noha = cpue_noha 446 self._cpue_nokm2 = cpue_nokm2 447 self._cpue_no1000km2 = cpue_no1000km2 448 self._weight_kg = weight_kg 449 self._count = count 450 self._bottom_temperature_c = bottom_temperature_c 451 self._surface_temperature_c = surface_temperature_c 452 self._depth_m = depth_m 453 self._distance_fished_km = distance_fished_km 454 self._net_width_m = net_width_m 455 self._net_height_m = net_height_m 456 self._area_swept_ha = area_swept_ha 457 self._duration_hr = duration_hr 458 self._tsn = tsn 459 self._ak_survey_id = ak_survey_id
Create a new record.
Arguments:
- year: Year for the survey in which this observation was made.
- srvy: The name of the survey in which this observation was made. NBS (N Bearing Sea), EBS (SE Bearing Sea), BSS (Bearing Sea Slope), or GOA (Gulf of Alaska)
- survey: Long form description of the survey in which the observation was made.
- survey_id: Unique numeric ID for the survey.
- cruise: An ID uniquely identifying the cruise in which the observation was made. Multiple cruises in a survey.
- haul: An ID uniquely identifying the haul in which this observation was made. Multiple hauls per cruises.
- stratum: Unique ID for statistical area / survey combination as described in the metadata or 0 if an experimental tow.
- station: Station associated with the survey.
- vessel_name: Unique ID describing the vessel that made this observation. This is left as a string but, in practice, is likely numeric.
- vessel_id: Name of the vessel at the time the observation was made with multiple names potentially associated with a vessel ID.
- date_time: The date and time of the haul which has been attempted to be transformed to an ISO 8601 string without timezone info. If it couldn’t be transformed, the original string.
- latitude_dd: Latitude in decimal degrees associated with the haul.
- longitude_dd: Longitude in decimal degrees associated with the haul.
- species_code: Unique ID associated with the species observed.
- common_name: The “common name” associated with the species observed.
- scientific_name: The “scientific name” associated with the species observed.
- taxon_confidence: Confidence flag regarding ability to identify species (High, Moderate, Low). In practice, this can also be Unassessed.
- cpue_kgha: Catch weight divided by net area (kg / hectares) if available. None if could not interpret as a float.
- cpue_kgkm2: Catch weight divided by net area (kg / km^2) if available. None if could not interpret as a float.
- cpue_kg1000km2: Catch weight divided by net area (kg / km^2 * 1000) if available. See metadata. None if could not interpret as a float.
- cpue_noha: Catch number divided by net sweep area if available (count / hectares). None if could not interpret as a float.
- cpue_nokm2: Catch number divided by net sweep area if available (count / km^2). See metadata. None if could not interpret as a float.
- cpue_no1000km2: Catch number divided by net sweep area if available (count / km^2 * 1000). See metadata. None if could not interpret as a float.
- weight_kg: Taxon weight (kg) if available. See metadata. None if could not interpret as a float.
- count: Total number of organism individuals in haul. None if could not interpret as a float.
- bottom_temperature_c: Bottom temperature associated with observation if available in Celsius. None if not given or could not interpret as a float.
- surface_temperature_c: Surface temperature associated with observation if available in Celsius. None if not given or could not interpret as a float.
- depth_m: Depth of the bottom in meters.
- distance_fished_km: Distance of the net fished as km.
- net_width_m: Distance of the net fished as m.
- net_height_m: Height of the net fished as m.
- area_swept_ha: Area covered by the net while fishing in hectares.
- duration_hr: Duration of the haul as number of hours.
- tsn: Taxonomic information system species code.
- ak_survey_id: AK identifier for the survey.
461 def get_year(self) -> float: 462 """Get the field labeled as year in the API. 463 464 Returns: 465 Year for the survey in which this observation was made. 466 """ 467 return self._year
Get the field labeled as year in the API.
Returns:
Year for the survey in which this observation was made.
469 def get_srvy(self) -> str: 470 """Get the field labeled as srvy in the API. 471 472 Returns: 473 The name of the survey in which this observation was made. NBS (N 474 Bearing Sea), EBS (SE Bearing Sea), BSS (Bearing Sea Slope), or GOA 475 (Gulf of Alaska) 476 """ 477 return self._srvy
Get the field labeled as srvy in the API.
Returns:
The name of the survey in which this observation was made. NBS (N Bearing Sea), EBS (SE Bearing Sea), BSS (Bearing Sea Slope), or GOA (Gulf of Alaska)
479 def get_survey(self) -> str: 480 """Get the field labeled as survey in the API. 481 482 Returns: 483 Long form description of the survey in which the observation was 484 made. 485 """ 486 return self._survey
Get the field labeled as survey in the API.
Returns:
Long form description of the survey in which the observation was made.
488 def get_survey_id(self) -> float: 489 """Get the field labeled as survey_id in the API. 490 491 Returns: 492 Unique numeric ID for the survey. 493 """ 494 return self._survey_id
Get the field labeled as survey_id in the API.
Returns:
Unique numeric ID for the survey.
496 def get_cruise(self) -> float: 497 """Get the field labeled as cruise in the API. 498 499 Returns: 500 An ID uniquely identifying the cruise in which the observation was 501 made. Multiple cruises in a survey. 502 """ 503 return self._cruise
Get the field labeled as cruise in the API.
Returns:
An ID uniquely identifying the cruise in which the observation was made. Multiple cruises in a survey.
505 def get_haul(self) -> float: 506 """Get the field labeled as haul in the API. 507 508 Returns: 509 An ID uniquely identifying the haul in which this observation was 510 made. Multiple hauls per cruises. 511 """ 512 return self._haul
Get the field labeled as haul in the API.
Returns:
An ID uniquely identifying the haul in which this observation was made. Multiple hauls per cruises.
514 def get_stratum(self) -> float: 515 """Get the field labeled as stratum in the API. 516 517 Returns: 518 Unique ID for statistical area / survey combination as described in 519 the metadata or 0 if an experimental tow. 520 """ 521 return self._stratum
Get the field labeled as stratum in the API.
Returns:
Unique ID for statistical area / survey combination as described in the metadata or 0 if an experimental tow.
523 def get_station(self) -> str: 524 """Get the field labeled as station in the API. 525 526 Returns: 527 Station associated with the survey. 528 """ 529 return self._station
Get the field labeled as station in the API.
Returns:
Station associated with the survey.
531 def get_vessel_name(self) -> str: 532 """Get the field labeled as vessel_name in the API. 533 534 Returns: 535 Unique ID describing the vessel that made this observation. This is 536 left as a string but, in practice, is likely numeric. 537 """ 538 return self._vessel_name
Get the field labeled as vessel_name in the API.
Returns:
Unique ID describing the vessel that made this observation. This is left as a string but, in practice, is likely numeric.
540 def get_vessel_id(self) -> float: 541 """Get the field labeled as vessel_id in the API. 542 543 Returns: 544 Name of the vessel at the time the observation was made with 545 multiple names potentially associated with a vessel ID. 546 """ 547 return self._vessel_id
Get the field labeled as vessel_id in the API.
Returns:
Name of the vessel at the time the observation was made with multiple names potentially associated with a vessel ID.
549 def get_date_time(self) -> str: 550 """Get the field labeled as date_time in the API. 551 552 Returns: 553 The date and time of the haul which has been attempted to be 554 transformed to an ISO 8601 string without timezone info. If it 555 couldn’t be transformed, the original string is reported. 556 """ 557 return self._date_time
Get the field labeled as date_time in the API.
Returns:
The date and time of the haul which has been attempted to be transformed to an ISO 8601 string without timezone info. If it couldn’t be transformed, the original string is reported.
559 def get_latitude(self, units: str = 'dd') -> float: 560 """Get the field labeled as latitude_dd in the API. 561 562 Args: 563 units: The units to return this value in. Only supported is dd for 564 degrees. Deafults to dd. 565 566 Returns: 567 Latitude in decimal degrees associated with the haul. 568 """ 569 return afscgap.model.assert_float_present( 570 afscgap.convert.convert_degrees(self._latitude_dd, units) 571 )
Get the field labeled as latitude_dd in the API.
Arguments:
- units: The units to return this value in. Only supported is dd for degrees. Deafults to dd.
Returns:
Latitude in decimal degrees associated with the haul.
573 def get_longitude(self, units: str = 'dd') -> float: 574 """Get the field labeled as longitude_dd in the API. 575 576 Args: 577 units: The units to return this value in. Only supported is dd for 578 degrees. Deafults to dd. 579 580 Returns: 581 Longitude in decimal degrees associated with the haul. 582 """ 583 return afscgap.model.assert_float_present( 584 afscgap.convert.convert_degrees(self._longitude_dd, units) 585 )
Get the field labeled as longitude_dd in the API.
Arguments:
- units: The units to return this value in. Only supported is dd for degrees. Deafults to dd.
Returns:
Longitude in decimal degrees associated with the haul.
587 def get_species_code(self) -> float: 588 """Get the field labeled as species_code in the API. 589 590 Returns: 591 Unique ID associated with the species observed. 592 """ 593 return self._species_code
Get the field labeled as species_code in the API.
Returns:
Unique ID associated with the species observed.
595 def get_common_name(self) -> str: 596 """Get the field labeled as common_name in the API. 597 598 Returns: 599 The “common name” associated with the species observed. Example: 600 Pacific glass shrimp. 601 """ 602 return self._common_name
Get the field labeled as common_name in the API.
Returns:
The “common name” associated with the species observed. Example: Pacific glass shrimp.
604 def get_scientific_name(self) -> str: 605 """Get the field labeled as scientific_name in the API. 606 607 Returns: 608 The “scientific name” associated with the species observed. Example: 609 Pasiphaea pacifica. 610 """ 611 return self._scientific_name
Get the field labeled as scientific_name in the API.
Returns:
The “scientific name” associated with the species observed. Example: Pasiphaea pacifica.
613 def get_taxon_confidence(self) -> str: 614 """Get the field labeled as taxon_confidence in the API. 615 616 Returns: 617 Confidence flag regarding ability to identify species (High, 618 Moderate, Low). In practice, this can also be Unassessed. 619 """ 620 return self._taxon_confidence
Get the field labeled as taxon_confidence in the API.
Returns:
Confidence flag regarding ability to identify species (High, Moderate, Low). In practice, this can also be Unassessed.
622 def get_cpue_weight_maybe(self, units: str = 'kg/ha') -> OPT_FLOAT: 623 """Get a field labeled as cpue_* in the API. 624 625 Args: 626 units: The desired units for the catch per unit effort. Options: 627 kg/ha, kg/km2, kg1000/km2. Defaults to kg/ha. 628 629 Returns: 630 Catch weight divided by net area (in given units) if available. See 631 metadata. None if could not interpret as a float. If an inferred 632 zero catch record, will be zero. 633 """ 634 return { 635 'kg/ha': self._cpue_kgha, 636 'kg/km2': self._cpue_kgkm2, 637 'kg1000/km2': self._cpue_kg1000km2 638 }[units]
Get a field labeled as cpue_* in the API.
Arguments:
- units: The desired units for the catch per unit effort. Options: kg/ha, kg/km2, kg1000/km2. Defaults to kg/ha.
Returns:
Catch weight divided by net area (in given units) if available. See metadata. None if could not interpret as a float. If an inferred zero catch record, will be zero.
640 def get_cpue_count_maybe(self, units: str = 'kg/ha') -> OPT_FLOAT: 641 """Get the field labeled as cpue_* in the API. 642 643 Get the catch per unit effort from the record with one of the following 644 units: kg/ha, kg/km2, kg1000/km2. 645 646 Args: 647 units: The desired units for the catch per unit effort. Options: 648 count/ha, count/km2, and count1000/km2. Defaults to count/ha. 649 650 Returns: 651 Catch weight divided by net area (in given units) if available. See 652 metadata. None if could not interpret as a float. If an inferred 653 zero catch record, will be zero. 654 """ 655 return { 656 'count/ha': self._cpue_noha, 657 'count/km2': self._cpue_nokm2, 658 'count1000/km2': self._cpue_no1000km2 659 }[units]
Get the field labeled as cpue_* in the API.
Get the catch per unit effort from the record with one of the following units: kg/ha, kg/km2, kg1000/km2.
Arguments:
- units: The desired units for the catch per unit effort. Options: count/ha, count/km2, and count1000/km2. Defaults to count/ha.
Returns:
Catch weight divided by net area (in given units) if available. See metadata. None if could not interpret as a float. If an inferred zero catch record, will be zero.
661 def get_weight_maybe(self, units: str = 'kg') -> OPT_FLOAT: 662 """Get the field labeled as weight_kg in the API. 663 664 Args: 665 units: The units in which the weight should be returned. Options are 666 g, kg for grams and kilograms respectively. Deafults to kg. 667 668 Returns: 669 Taxon weight if available. See metadata. None if could not 670 interpret as a float. If an inferred zero catch record, will be 671 zero. 672 """ 673 return afscgap.convert.convert_weight(self._weight_kg, units)
Get the field labeled as weight_kg in the API.
Arguments:
- units: The units in which the weight should be returned. Options are g, kg for grams and kilograms respectively. Deafults to kg.
Returns:
Taxon weight if available. See metadata. None if could not interpret as a float. If an inferred zero catch record, will be zero.
675 def get_count_maybe(self) -> OPT_FLOAT: 676 """Get the field labeled as count in the API. 677 678 Returns: 679 Total number of organism individuals in haul. None if could not 680 interpret as a float. 681 """ 682 return self._count
Get the field labeled as count in the API.
Returns:
Total number of organism individuals in haul. None if could not interpret as a float.
684 def get_bottom_temperature_maybe(self, units: str = 'c') -> OPT_FLOAT: 685 """Get the field labeled as bottom_temperature_c in the API. 686 687 Args: 688 units: The units in which the temperature should be returned. 689 Options: c or f for Celcius and Fahrenheit respectively. 690 Defaults to c. 691 692 Returns: 693 Bottom temperature associated with observation / inferrence if 694 available in desired units. None if not given or could not interpret 695 as a float. 696 """ 697 return afscgap.convert.convert_temperature( 698 self._bottom_temperature_c, 699 units 700 )
Get the field labeled as bottom_temperature_c in the API.
Arguments:
- units: The units in which the temperature should be returned. Options: c or f for Celcius and Fahrenheit respectively. Defaults to c.
Returns:
Bottom temperature associated with observation / inferrence if available in desired units. None if not given or could not interpret as a float.
702 def get_surface_temperature_maybe(self, units: str = 'c') -> OPT_FLOAT: 703 """Get the field labeled as surface_temperature_c in the API. 704 705 Args: 706 units: The units in which the temperature should be returned. 707 Options: c or f for Celcius and Fahrenheit respectively. 708 Defaults to c. 709 710 Returns: 711 Surface temperature associated with observation / inferrence if 712 available. None if not given or could not interpret as a float. 713 """ 714 return afscgap.convert.convert_temperature( 715 self._surface_temperature_c, 716 units 717 )
Get the field labeled as surface_temperature_c in the API.
Arguments:
- units: The units in which the temperature should be returned. Options: c or f for Celcius and Fahrenheit respectively. Defaults to c.
Returns:
Surface temperature associated with observation / inferrence if available. None if not given or could not interpret as a float.
719 def get_depth(self, units: str = 'm') -> float: 720 """Get the field labeled as depth_m in the API. 721 722 Args: 723 units: The units in which the distance should be returned. Options: 724 m or km for meters and kilometers respectively. Defaults to m. 725 726 Returns: 727 Depth of the bottom. 728 """ 729 return afscgap.model.assert_float_present( 730 afscgap.convert.convert_distance(self._depth_m, units) 731 )
Get the field labeled as depth_m in the API.
Arguments:
- units: The units in which the distance should be returned. Options: m or km for meters and kilometers respectively. Defaults to m.
Returns:
Depth of the bottom.
733 def get_distance_fished(self, units: str = 'm') -> float: 734 """Get the field labeled as distance_fished_km in the API. 735 736 Args: 737 units: The units in which the distance should be returned. Options: 738 m or km for meters and kilometers respectively. Defaults to m. 739 740 Returns: 741 Distance of the net fished. 742 """ 743 return afscgap.model.assert_float_present( 744 afscgap.convert.convert_distance( 745 self._distance_fished_km * 1000, 746 units 747 ) 748 )
Get the field labeled as distance_fished_km in the API.
Arguments:
- units: The units in which the distance should be returned. Options: m or km for meters and kilometers respectively. Defaults to m.
Returns:
Distance of the net fished.
750 def get_net_width_maybe(self, units: str = 'm') -> OPT_FLOAT: 751 """Get the field labeled as net_width_m in the API. 752 753 Args: 754 units: The units in which the distance should be returned. Options: 755 m or km for meters and kilometers respectively. Defaults to m. 756 757 Returns: 758 Distance of the net fished or None if not given. 759 """ 760 return afscgap.convert.convert_distance( 761 self._net_width_m, 762 units 763 )
Get the field labeled as net_width_m in the API.
Arguments:
- units: The units in which the distance should be returned. Options: m or km for meters and kilometers respectively. Defaults to m.
Returns:
Distance of the net fished or None if not given.
765 def get_net_height_maybe(self, units: str = 'm') -> OPT_FLOAT: 766 """Get the field labeled as net_height_m in the API. 767 768 Args: 769 units: The units in which the distance should be returned. Options: 770 m or km for meters and kilometers respectively. Defaults to m. 771 772 Returns: 773 Height of the net fished or None if not given. 774 """ 775 return afscgap.convert.convert_distance( 776 self._net_height_m, 777 units 778 )
Get the field labeled as net_height_m in the API.
Arguments:
- units: The units in which the distance should be returned. Options: m or km for meters and kilometers respectively. Defaults to m.
Returns:
Height of the net fished or None if not given.
780 def get_net_width(self, units: str = 'm') -> float: 781 """Get the field labeled as net_width_m in the API. 782 783 Args: 784 units: The units in which the distance should be returned. Options: 785 m or km for meters and kilometers respectively. Defaults to m. 786 787 Returns: 788 Distance of the net fished after asserting it is given. 789 """ 790 return afscgap.model.assert_float_present( 791 self.get_net_width_maybe(units=units) 792 )
Get the field labeled as net_width_m in the API.
Arguments:
- units: The units in which the distance should be returned. Options: m or km for meters and kilometers respectively. Defaults to m.
Returns:
Distance of the net fished after asserting it is given.
794 def get_net_height(self, units: str = 'm') -> float: 795 """Get the field labeled as net_height_m in the API. 796 797 Args: 798 units: The units in which the distance should be returned. Options: 799 m or km for meters and kilometers respectively. Defaults to m. 800 801 Returns: 802 Height of the net fished after asserting it is given. 803 """ 804 return afscgap.model.assert_float_present( 805 self.get_net_height_maybe(units=units) 806 )
Get the field labeled as net_height_m in the API.
Arguments:
- units: The units in which the distance should be returned. Options: m or km for meters and kilometers respectively. Defaults to m.
Returns:
Height of the net fished after asserting it is given.
808 def get_area_swept(self, units: str = 'ha') -> float: 809 """Get the field labeled as area_swept_ha in the API. 810 811 Args: 812 units: The units in which the area should be returned. Options: 813 ha, m2, km2. Defaults to ha. 814 815 Returns: 816 Area covered by the net while fishing in desired units. 817 """ 818 return afscgap.model.assert_float_present( 819 afscgap.convert.convert_area(self._area_swept_ha, units) 820 )
Get the field labeled as area_swept_ha in the API.
Arguments:
- units: The units in which the area should be returned. Options: ha, m2, km2. Defaults to ha.
Returns:
Area covered by the net while fishing in desired units.
822 def get_duration(self, units: str = 'hr') -> float: 823 """Get the field labeled as duration_hr in the API. 824 825 Args: 826 units: The units in which the duration should be returned. Options: 827 day, hr, min. Defaults to hr. 828 829 Returns: 830 Duration of the haul. 831 """ 832 return afscgap.model.assert_float_present( 833 afscgap.convert.convert_time(self._duration_hr, units) 834 )
Get the field labeled as duration_hr in the API.
Arguments:
- units: The units in which the duration should be returned. Options: day, hr, min. Defaults to hr.
Returns:
Duration of the haul.
836 def get_tsn(self) -> int: 837 """Get the field labeled as tsn in the API. 838 839 Returns: 840 Taxonomic information system species code. 841 """ 842 return afscgap.model.assert_int_present(self._tsn)
Get the field labeled as tsn in the API.
Returns:
Taxonomic information system species code.
844 def get_tsn_maybe(self) -> OPT_INT: 845 """Get the field labeled as tsn in the API or None. 846 847 Returns: 848 Taxonomic information system species code if it could be parsed as 849 an int and None otherwise. 850 """ 851 return self._tsn
Get the field labeled as tsn in the API or None.
Returns:
Taxonomic information system species code if it could be parsed as an int and None otherwise.
853 def get_ak_survey_id(self) -> int: 854 """Get the field labeled as ak_survey_id in the API. 855 856 Returns: 857 AK identifier for the survey. 858 """ 859 return self._ak_survey_id
Get the field labeled as ak_survey_id in the API.
Returns:
AK identifier for the survey.
861 def get_ak_survey_id_maybe(self) -> OPT_INT: 862 """Get the field labeled as ak_survey_id in the API. 863 864 Returns: 865 AK identifier for the survey or None if not given. 866 """ 867 return self._ak_survey_id
Get the field labeled as ak_survey_id in the API.
Returns:
AK identifier for the survey or None if not given.
869 def get_cpue_weight(self, units: str = 'kg/ha') -> float: 870 """Get the value of field cpue_kgha with validity assert. 871 872 Args: 873 units: The desired units for the catch per unit effort. Options: 874 kg/ha, kg/km2, kg1000/km2. Defaults to kg/ha. 875 876 Raises: 877 AssertionError: Raised if this field was not given by the API or 878 could not be parsed as expected. 879 880 Returns: 881 Catch weight divided by net area (kg / hectares) if available. See 882 metadata. 883 """ 884 return afscgap.model.assert_float_present( 885 self.get_cpue_weight_maybe(units=units) 886 )
Get the value of field cpue_kgha with validity assert.
Arguments:
- units: The desired units for the catch per unit effort. Options: kg/ha, kg/km2, kg1000/km2. Defaults to kg/ha.
Raises:
- AssertionError: Raised if this field was not given by the API or
- could not be parsed as expected.
Returns:
Catch weight divided by net area (kg / hectares) if available. See metadata.
888 def get_cpue_count(self, units: str = 'count/ha') -> float: 889 """Get the value of field cpue_noha with validity assert. 890 891 Args: 892 units: The desired units for the catch per unit effort. Options: 893 count/ha, count/km2, and count1000/km2. Defaults to count/ha. 894 895 Raises: 896 AssertionError: Raised if this field was not given by the API or 897 could not be parsed as expected. 898 899 Returns: 900 Catch number divided by net sweep area if available (count / 901 hectares). See metadata. 902 """ 903 return afscgap.model.assert_float_present( 904 self.get_cpue_count_maybe(units=units) 905 )
Get the value of field cpue_noha with validity assert.
Arguments:
- units: The desired units for the catch per unit effort. Options: count/ha, count/km2, and count1000/km2. Defaults to count/ha.
Raises:
- AssertionError: Raised if this field was not given by the API or
- could not be parsed as expected.
Returns:
Catch number divided by net sweep area if available (count / hectares). See metadata.
907 def get_weight(self, units: str = 'kg') -> float: 908 """Get the value of field weight_kg with validity assert. 909 910 Args: 911 units: The units in which the weight should be returned. Options are 912 g, kg for grams and kilograms respectively. Deafults to kg. 913 914 Raises: 915 AssertionError: Raised if this field was not given by the API or 916 could not be parsed as expected. 917 918 Returns: 919 Taxon weight (kg) if available. See metadata. 920 """ 921 return afscgap.model.assert_float_present( 922 self.get_weight_maybe(units=units) 923 )
Get the value of field weight_kg with validity assert.
Arguments:
- units: The units in which the weight should be returned. Options are g, kg for grams and kilograms respectively. Deafults to kg.
Raises:
- AssertionError: Raised if this field was not given by the API or
- could not be parsed as expected.
Returns:
Taxon weight (kg) if available. See metadata.
925 def get_count(self) -> float: 926 """Get the value of field count with validity assert. 927 928 Raises: 929 AssertionError: Raised if this field was not given by the API or 930 could not be parsed as expected. 931 932 Returns: 933 Total number of organism individuals in haul. 934 """ 935 return afscgap.model.assert_float_present(self._count)
Get the value of field count with validity assert.
Raises:
- AssertionError: Raised if this field was not given by the API or
- could not be parsed as expected.
Returns:
Total number of organism individuals in haul.
937 def get_bottom_temperature(self, units='c') -> float: 938 """Get the value of field bottom_temperature_c with validity assert. 939 940 Args: 941 units: The units in which the temperature should be returned. 942 Options: c or f for Celcius and Fahrenheit respectively. 943 Defaults to c. 944 945 Raises: 946 AssertionError: Raised if this field was not given by the API or 947 could not be parsed as expected. 948 949 Returns: 950 Bottom temperature associated with observation / inferrence if 951 available. 952 """ 953 return afscgap.model.assert_float_present( 954 self.get_bottom_temperature_maybe(units=units) 955 )
Get the value of field bottom_temperature_c with validity assert.
Arguments:
- units: The units in which the temperature should be returned. Options: c or f for Celcius and Fahrenheit respectively. Defaults to c.
Raises:
- AssertionError: Raised if this field was not given by the API or
- could not be parsed as expected.
Returns:
Bottom temperature associated with observation / inferrence if available.
957 def get_surface_temperature(self, units='c') -> float: 958 """Get the value of field surface_temperature_c with validity assert. 959 960 Args: 961 units: The units in which the temperature should be returned. 962 Options: c or f for Celcius and Fahrenheit respectively. 963 Defaults to c. 964 965 Raises: 966 AssertionError: Raised if this field was not given by the API or 967 could not be parsed as expected. 968 969 Returns: 970 Surface temperature associated with observation / inferrence if 971 available. 972 """ 973 return afscgap.model.assert_float_present( 974 self.get_surface_temperature_maybe(units=units) 975 )
Get the value of field surface_temperature_c with validity assert.
Arguments:
- units: The units in which the temperature should be returned. Options: c or f for Celcius and Fahrenheit respectively. Defaults to c.
Raises:
- AssertionError: Raised if this field was not given by the API or
- could not be parsed as expected.
Returns:
Surface temperature associated with observation / inferrence if available.
977 def is_complete(self) -> bool: 978 """Determine if this record has all of its values filled in. 979 980 Returns: 981 True if all optional fields have a parsed value with the expected 982 type and false otherwise. 983 """ 984 optional_fields = [ 985 self._cpue_kgha, 986 self._cpue_kgkm2, 987 self._cpue_kg1000km2, 988 self._cpue_noha, 989 self._cpue_nokm2, 990 self._cpue_no1000km2, 991 self._weight_kg, 992 self._count, 993 self._bottom_temperature_c, 994 self._surface_temperature_c, 995 self._tsn 996 ] 997 998 has_none = None in optional_fields 999 all_fields_present = not has_none 1000 has_valid_date_time = afscgap.convert.is_iso8601(self._date_time) 1001 1002 return all_fields_present and has_valid_date_time
Determine if this record has all of its values filled in.
Returns:
True if all optional fields have a parsed value with the expected type and false otherwise.
Inherited Members
1005def parse_record(target: dict) -> afscgap.model.Record: 1006 """Parse a record from a returned item dictionary. 1007 1008 Args: 1009 target: The dictionary from which values should be read. 1010 1011 Raises: 1012 ValueError: Exception raised if a field has an unexpected type or cannot 1013 be parsed to an expected type. 1014 KeyError: Exception raised if an expected field is not found. 1015 1016 Returns: 1017 Newly parsed record. 1018 """ 1019 year = float(target['year']) 1020 srvy = str(target['srvy']) 1021 survey = str(target['survey']) 1022 survey_id = float(target['survey_id']) 1023 cruise = float(target['cruise']) 1024 haul = float(target['haul']) 1025 stratum = float(target['stratum']) 1026 station = str(target['station']) 1027 vessel_name = str(target['vessel_name']) 1028 vessel_id = float(target['vessel_id']) 1029 date_time = afscgap.convert.convert_to_iso8601(str(target['date_time'])) 1030 latitude_dd = float(target['latitude_dd']) 1031 longitude_dd = float(target['longitude_dd']) 1032 species_code = float(target['species_code']) 1033 common_name = str(target['common_name']) 1034 scientific_name = str(target['scientific_name']) 1035 taxon_confidence = str(target['taxon_confidence']) 1036 cpue_kgha = afscgap.model.get_opt_float(target['cpue_kgha']) 1037 cpue_kgkm2 = afscgap.model.get_opt_float(target['cpue_kgkm2']) 1038 cpue_kg1000km2 = afscgap.model.get_opt_float(target['cpue_kg1000km2']) 1039 cpue_noha = afscgap.model.get_opt_float(target['cpue_noha']) 1040 cpue_nokm2 = afscgap.model.get_opt_float(target['cpue_nokm2']) 1041 cpue_no1000km2 = afscgap.model.get_opt_float(target['cpue_no1000km2']) 1042 weight_kg = afscgap.model.get_opt_float(target['weight_kg']) 1043 count = afscgap.model.get_opt_float(target['count']) 1044 bottom_temperature_c = afscgap.model.get_opt_float( 1045 target['bottom_temperature_c'] 1046 ) 1047 surface_temperature_c = afscgap.model.get_opt_float( 1048 target['surface_temperature_c'] 1049 ) 1050 depth_m = float(target['depth_m']) 1051 distance_fished_km = float(target['distance_fished_km']) 1052 net_width_m = afscgap.model.get_opt_float(target['net_width_m']) 1053 net_height_m = afscgap.model.get_opt_float(target['net_height_m']) 1054 area_swept_ha = float(target['area_swept_ha']) 1055 duration_hr = float(target['duration_hr']) 1056 tsn = afscgap.model.get_opt_int(target['tsn']) 1057 ak_survey_id = int(target['ak_survey_id']) 1058 1059 return ApiRecord( 1060 year, 1061 srvy, 1062 survey, 1063 survey_id, 1064 cruise, 1065 haul, 1066 stratum, 1067 station, 1068 vessel_name, 1069 vessel_id, 1070 date_time, 1071 latitude_dd, 1072 longitude_dd, 1073 species_code, 1074 common_name, 1075 scientific_name, 1076 taxon_confidence, 1077 cpue_kgha, 1078 cpue_kgkm2, 1079 cpue_kg1000km2, 1080 cpue_noha, 1081 cpue_nokm2, 1082 cpue_no1000km2, 1083 weight_kg, 1084 count, 1085 bottom_temperature_c, 1086 surface_temperature_c, 1087 depth_m, 1088 distance_fished_km, 1089 net_width_m, 1090 net_height_m, 1091 area_swept_ha, 1092 duration_hr, 1093 tsn, 1094 ak_survey_id 1095 )
Parse a record from a returned item dictionary.
Arguments:
- target: The dictionary from which values should be read.
Raises:
- ValueError: Exception raised if a field has an unexpected type or cannot be parsed to an expected type.
- KeyError: Exception raised if an expected field is not found.
Returns:
Newly parsed record.
1098class ParseRecordResult: 1099 """Object with the results of trying to parse a record from the API. 1100 1101 Object with the results of trying to parse a record from the API, allowing 1102 for internal record keeping within the afscgap library. Note that this is 1103 an internal data structure and not expected to reach client code. 1104 """ 1105 1106 def __init__(self, raw_record: dict, 1107 parsed: typing.Optional[afscgap.model.Record]): 1108 """Create a new record of a parse attempt. 1109 1110 Args: 1111 raw_record: Item from the API's JSON response payload that the 1112 library attempted to parse. 1113 parsed: The Record read if successful or None if it could not be 1114 parsed. 1115 """ 1116 self._raw_record = raw_record 1117 self._parsed = parsed 1118 1119 def get_raw_record(self) -> dict: 1120 """Get the input raw JSON record. 1121 1122 Returns: 1123 Item from the API's JSON response payload that the library attempted 1124 to parse. 1125 """ 1126 return self._raw_record 1127 1128 def get_parsed(self) -> typing.Optional[afscgap.model.Record]: 1129 """Get the record that was parsed if successful. 1130 1131 Returns: 1132 The Record read if successful or None if it could not be parsed. 1133 """ 1134 return self._parsed 1135 1136 def meets_requirements(self, allow_incomplete: bool) -> bool: 1137 """Determine if this record is "valid" according to the client code. 1138 1139 Args: 1140 allow_incomplete: Flag indicating if incomplete records are 1141 considered valid. If true, incomplete records will be considered 1142 valid. If false, incomplete records will be considered invalid. 1143 Incomplete means missing any optional fields or failing to 1144 achieve an ISO 8601 date_time value. 1145 1146 Returns: 1147 True if this record was parsed successfully and meets the 1148 requirements specified for being considered "valid" per the 1149 allow_incomplete flag. False otherwise. 1150 """ 1151 if self._parsed is None: 1152 return False 1153 1154 return allow_incomplete or self._parsed.is_complete()
Object with the results of trying to parse a record from the API.
Object with the results of trying to parse a record from the API, allowing for internal record keeping within the afscgap library. Note that this is an internal data structure and not expected to reach client code.
1106 def __init__(self, raw_record: dict, 1107 parsed: typing.Optional[afscgap.model.Record]): 1108 """Create a new record of a parse attempt. 1109 1110 Args: 1111 raw_record: Item from the API's JSON response payload that the 1112 library attempted to parse. 1113 parsed: The Record read if successful or None if it could not be 1114 parsed. 1115 """ 1116 self._raw_record = raw_record 1117 self._parsed = parsed
Create a new record of a parse attempt.
Arguments:
- raw_record: Item from the API's JSON response payload that the library attempted to parse.
- parsed: The Record read if successful or None if it could not be parsed.
1119 def get_raw_record(self) -> dict: 1120 """Get the input raw JSON record. 1121 1122 Returns: 1123 Item from the API's JSON response payload that the library attempted 1124 to parse. 1125 """ 1126 return self._raw_record
Get the input raw JSON record.
Returns:
Item from the API's JSON response payload that the library attempted to parse.
1128 def get_parsed(self) -> typing.Optional[afscgap.model.Record]: 1129 """Get the record that was parsed if successful. 1130 1131 Returns: 1132 The Record read if successful or None if it could not be parsed. 1133 """ 1134 return self._parsed
Get the record that was parsed if successful.
Returns:
The Record read if successful or None if it could not be parsed.
1136 def meets_requirements(self, allow_incomplete: bool) -> bool: 1137 """Determine if this record is "valid" according to the client code. 1138 1139 Args: 1140 allow_incomplete: Flag indicating if incomplete records are 1141 considered valid. If true, incomplete records will be considered 1142 valid. If false, incomplete records will be considered invalid. 1143 Incomplete means missing any optional fields or failing to 1144 achieve an ISO 8601 date_time value. 1145 1146 Returns: 1147 True if this record was parsed successfully and meets the 1148 requirements specified for being considered "valid" per the 1149 allow_incomplete flag. False otherwise. 1150 """ 1151 if self._parsed is None: 1152 return False 1153 1154 return allow_incomplete or self._parsed.is_complete()
Determine if this record is "valid" according to the client code.
Arguments:
- allow_incomplete: Flag indicating if incomplete records are considered valid. If true, incomplete records will be considered valid. If false, incomplete records will be considered invalid. Incomplete means missing any optional fields or failing to achieve an ISO 8601 date_time value.
Returns:
True if this record was parsed successfully and meets the requirements specified for being considered "valid" per the allow_incomplete flag. False otherwise.
1157def try_parse(target: dict) -> ParseRecordResult: 1158 """Attempt parsing a Record from an input item dictionary from the API. 1159 1160 Params: 1161 target: The dictionary from which to parse. This should be an item 1162 from the items array in the returned JSON payload from the API. 1163 1164 Returns: 1165 Parse result describing if the dictionary was parsed successfully. 1166 """ 1167 try: 1168 return ParseRecordResult(target, parse_record(target)) 1169 except (ValueError, KeyError): 1170 return ParseRecordResult(target, None)
Attempt parsing a Record from an input item dictionary from the API.
Arguments:
- target: The dictionary from which to parse. This should be an item from the items array in the returned JSON payload from the API.
Returns:
Parse result describing if the dictionary was parsed successfully.