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)
DEFAULT_DOMAIN = 'https://apps-st.fisheries.noaa.gov'
DEFAULT_URL = 'https://apps-st.fisheries.noaa.gov/ods/foss/afsc_groundfish_survey/'
def build_api_cursor( params: dict, limit: Optional[int] = None, start_offset: Optional[int] = None, filter_incomplete: bool = False, requestor: Optional[Callable[[str], requests.models.Response]] = None, base_url: Optional[str] = None) -> afscgap.cursor.Cursor:
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.

def get_query_url(params: dict, base: Optional[str] = None) -> str:
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.

class ApiServiceCursor(typing.Iterable[afscgap.model.Record]):
 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.

ApiServiceCursor( query_url: str, limit: Optional[int] = None, start_offset: Optional[int] = None, filter_incomplete: bool = False, requestor: Optional[Callable[[str], requests.models.Response]] = None)
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.
def get_base_url(self) -> str:
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.

def get_limit(self) -> Optional[int]:
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.

def get_start_offset(self) -> Optional[int]:
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.

def get_filtering_incomplete(self) -> bool:
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.

def get_page_url(self, offset: Optional[int] = None, limit: Optional[int] = None) -> str:
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.

def get_page( self, offset: Optional[int] = None, limit: Optional[int] = None, ignore_invalid: bool = False) -> List[afscgap.model.Record]:
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.

def get_invalid(self) -> queue.Queue[dict]:
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.

def to_dicts(self) -> Iterable[dict]:
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.

def get_next(self) -> Optional[afscgap.model.Record]:
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.

class ApiRecord(afscgap.model.Record):
 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.

ApiRecord( year: float, srvy: str, survey: str, survey_id: float, cruise: float, haul: float, stratum: float, station: str, vessel_name: str, vessel_id: float, date_time: str, latitude_dd: float, longitude_dd: float, species_code: float, common_name: str, scientific_name: str, taxon_confidence: str, cpue_kgha: Optional[float], cpue_kgkm2: Optional[float], cpue_kg1000km2: Optional[float], cpue_noha: Optional[float], cpue_nokm2: Optional[float], cpue_no1000km2: Optional[float], weight_kg: Optional[float], count: Optional[float], bottom_temperature_c: Optional[float], surface_temperature_c: Optional[float], depth_m: float, distance_fished_km: float, net_width_m: Optional[float], net_height_m: Optional[float], area_swept_ha: float, duration_hr: float, tsn: Optional[int], ak_survey_id: int)
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.
def get_year(self) -> float:
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.

def get_srvy(self) -> str:
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)

def get_survey(self) -> str:
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.

def get_survey_id(self) -> float:
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.

def get_cruise(self) -> float:
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.

def get_haul(self) -> float:
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.

def get_stratum(self) -> float:
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.

def get_station(self) -> str:
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.

def get_vessel_name(self) -> str:
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.

def get_vessel_id(self) -> float:
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.

def get_date_time(self) -> str:
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.

def get_latitude(self, units: str = 'dd') -> float:
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.

def get_longitude(self, units: str = 'dd') -> float:
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.

def get_species_code(self) -> float:
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.

def get_common_name(self) -> str:
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.

def get_scientific_name(self) -> str:
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.

def get_taxon_confidence(self) -> str:
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.

def get_cpue_weight_maybe(self, units: str = 'kg/ha') -> Optional[float]:
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.

def get_cpue_count_maybe(self, units: str = 'kg/ha') -> Optional[float]:
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.

def get_weight_maybe(self, units: str = 'kg') -> Optional[float]:
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.

def get_count_maybe(self) -> Optional[float]:
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.

def get_bottom_temperature_maybe(self, units: str = 'c') -> Optional[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.

def get_surface_temperature_maybe(self, units: str = 'c') -> Optional[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.

def get_depth(self, units: str = 'm') -> 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.

def get_distance_fished(self, units: str = 'm') -> float:
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.

def get_net_width_maybe(self, units: str = 'm') -> Optional[float]:
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.

def get_net_height_maybe(self, units: str = 'm') -> Optional[float]:
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.

def get_net_width(self, units: str = 'm') -> float:
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.

def get_net_height(self, units: str = 'm') -> float:
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.

def get_area_swept(self, units: str = 'ha') -> float:
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.

def get_duration(self, units: str = 'hr') -> float:
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.

def get_tsn(self) -> int:
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.

def get_tsn_maybe(self) -> Optional[int]:
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.

def get_ak_survey_id(self) -> int:
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.

def get_ak_survey_id_maybe(self) -> Optional[int]:
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.

def get_cpue_weight(self, units: str = 'kg/ha') -> float:
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.

def get_cpue_count(self, units: str = 'count/ha') -> float:
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.

def get_weight(self, units: str = 'kg') -> float:
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.

def get_count(self) -> float:
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.

def get_bottom_temperature(self, units='c') -> float:
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.

def get_surface_temperature(self, units='c') -> float:
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.

def is_complete(self) -> bool:
 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
afscgap.model.Record
to_dict
def parse_record(target: dict) -> afscgap.model.Record:
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.

class ParseRecordResult:
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.

ParseRecordResult(raw_record: dict, parsed: Optional[afscgap.model.Record])
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.
def get_raw_record(self) -> dict:
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.

def get_parsed(self) -> Optional[afscgap.model.Record]:
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.

def meets_requirements(self, allow_incomplete: bool) -> bool:
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.

def try_parse(target: dict) -> ParseRecordResult:
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.