def get(self, zone, *args): """ --- summary: Get zone GeoJSON description: Get the GeoJSON for a requested climate zone. tags: - Climate Zones parameters: - zone responses: 200: description: The GeoJSON definition for the climate zone content: application/geo+json: schema: type: object 401: $ref: '#/components/responses/401-Unauthorized' 404: $ref: '#/components/responses/404-NotFound' """ storage = get_storage() geojson = storage.read_climate_zone(zone.replace('+', ' ')) response = make_response(json.dumps(geojson), 200) response.mimetype = 'application/geo+json' return response
def validate_user_existence(): """Ensures users exist in both auth0 and the database Returns ------- True If the user already existed, or was created. False If the user does not have a verified auth0 account or If the /userinfo endpoint fails. See notes for details. Notes ----- Since a JWT hold it's own expiration, it is possible for a user to be deleted in auth0 and still carry a valid token. In order to avoid adding that use back to our database, we return false if the request for user info fails. """ from sfa_api.utils.storage import get_storage storage = get_storage() if not storage.user_exists(): try: info = request_user_info() except (requests.exceptions.HTTPError, JSONDecodeError): return False else: if not info.get('email_verified', False): # User has a valid token, but their email # is yet to be verified return False storage.create_new_user() return True
def post(self, report_id): """ --- summary: Store Raw Report tags: - Reports requestBody: description: JSON object containing the raw report. content: application/json: schema: $ref: '#/components/schemas/RawReportSchema' parameters: - report_id responses: 204: description: Updated status successfully. 401: $ref: '#/components/responses/401-Unauthorized' 404: $ref: '#components/responses/404-NotFound' """ raw_report_dict = request.get_json() raw_report = RawReportSchema().load(raw_report_dict) keep_ids = _extract_value_ids(raw_report) storage = get_storage() storage.store_raw_report(report_id, raw_report, keep_ids) return '', 204
def get(self, object_type): """ --- summary: List all permitted actions on all objects of a given type. description: |- Get a list of object ids and the actions the user is permitted to perform on each object. parameters: - object_type tags: - Users responses: 200: description: List of actions the user can make on the object. content: application/json: schema: $ref: '#/components/schemas/ActionsOnTypeList' 401: $ref: '#/components/responses/401-Unauthorized' 404: $ref: '#/components/responses/404-NotFound' """ if object_type not in ALLOWED_OBJECT_TYPES: raise BadAPIRequest({ 'object_type': 'Must be one of: ' f'{", ".join(ALLOWED_OBJECT_TYPES)}' }) storage = get_storage() object_dict = storage.list_actions_on_all_objects_of_type(object_type) json_response = {'object_type': object_type, 'objects': object_dict} return Response(ActionsOnTypeList().dumps(json_response), mimetype="application/json")
def get(self, email): """ --- summary: Get User Metadata by email. parameters: - email tags: - Users-By-Email responses: 200: description: User successully retrieved. content: application/json: schema: $ref: '#/components/schemas/UserSchema' 401: $ref: '#/components/responses/401-Unauthorized' 404: $ref: '#/components/responses/404-NotFound' """ storage = get_storage() auth0_id = get_auth0_id_of_user(email) user_id = storage.read_user_id(auth0_id) user = storage.read_user(user_id) user['email'] = email return jsonify(UserSchema().dump(user))
def get(self, *args): """ --- summary: Find zones description: Find all zones that the given point falls within tags: - Climate Zones parameters: - latitude - longitude responses: 200: description: Sucessfully retrieved zones. content: application/json: schema: type: array items: $ref: '#/components/schemas/ZoneMetadata' 400: $ref: '#/components/responses/400-BadRequest' 401: $ref: '#/components/responses/401-Unauthorized' """ lat, lon = validate_latitude_longitude() storage = get_storage() zones = storage.find_climate_zones(lat, lon) return jsonify(ZoneListSchema(many=True).dump(zones))
def get(self): """ --- summary: List object types user can create. description: |- Get a list of object types the user can create. tags: - Users responses: 200: description: List of object types the user has permission to create content: application/json: schema: $ref: '#/components/schemas/UserCreatePerms' 401: $ref: '#/components/responses/401-Unauthorized' 404: $ref: '#/components/responses/404-NotFound' """ storage = get_storage() object_types = storage.get_user_creatable_types() return Response(UserCreatePerms().dumps({'can_create': object_types}), mimetype="application/json")
def delete(self, email, role_id): """ --- summary: Remove a role from a User by email. parameters: - email - role_id tags: - Users-By-Email - Roles responses: 204: description: Role removed successfully.. 401: $ref: '#/components/responses/401-Unauthorized' 404: $ref: '#/components/responses/404-NotFound' """ storage = get_storage() auth0_id = get_auth0_id_of_user(email) try: user_id = storage.read_user_id(auth0_id) except StorageAuthError: return '', 204 return super().delete(user_id, role_id)
def get(self, observation_id, *args): """ --- summary: Get the time range of an Observation. description: | Get the minimum and maximum timestamps of Observation values stored in the Arbiter. tags: - Observations parameters: - observation_id responses: 200: description: Observation time range retrieved successfully. content: application/json: schema: $ref: '#/components/schemas/ObservationTimeRange' 401: $ref: '#/components/responses/401-Unauthorized' 404: $ref: '#/components/responses/404-NotFound' """ storage = get_storage() timerange = storage.read_observation_time_range(observation_id) timerange['observation_id'] = observation_id data = ObservationTimeRangeSchema().dump(timerange) return jsonify(data)
def post(self, report_id, status): """ --- summary: Update the report status tags: - Reports parameters: - report_id - status responses: 204: description: Updated status successfully. 401: $ref: '#/components/responses/401-Unauthorized' 404: $ref: '#components/responses/404-NotFound' """ if status not in REPORT_STATUS_OPTIONS: raise BadAPIRequest({ 'status': 'Must be one of ' '{",".join(REPORT_STATUS_OPTIONS)}.' }) storage = get_storage() storage.store_report_status(report_id, status) return '', 204
def get(self, observation_id, *args): """ --- summary: Get the gaps in Observation data. description: | Get the timestamps indicating where gaps in Observation data between start and end. tags: - Observations parameters: - observation_id - start_time - end_time responses: 200: description: Observation value gap retrieved successfully. content: application/json: schema: $ref: '#/components/schemas/ObservationValueGap' 401: $ref: '#/components/responses/401-Unauthorized' 404: $ref: '#/components/responses/404-NotFound' """ start, end = validate_start_end() storage = get_storage() out = { 'gaps': storage.find_observation_gaps(observation_id, start, end), 'observation_id': observation_id } data = ObservationGapSchema().dump(out) return jsonify(data)
def get(self, object_id): """ --- summary: Available actions on object. description: |- Get a list of actions the current user is allowed to perform on an object. parameters: - object_id tags: - Users responses: 200: description: List of actions the user can make on the object. content: application/json: schema: $ref: '#/components/schemas/ActionList' 401: $ref: '#/components/responses/401-Unauthorized' 404: $ref: '#/components/responses/404-NotFound' """ storage = get_storage() actions = storage.get_user_actions_on_object(object_id) json_response = {'object_id': object_id, 'actions': actions} return jsonify(ActionList().dump(json_response))
def get(self, observation_id, *args): """ --- summary: Get latest Observation data. description: | Get the most recent timeseries value from the Observation entry. tags: - Observations parameters: - observation_id responses: 200: description: Observation latest value retrieved successfully. content: application/json: schema: $ref: '#/components/schemas/ObservationValues' 401: $ref: '#/components/responses/401-Unauthorized' 404: $ref: '#/components/responses/404-NotFound' """ storage = get_storage() values = storage.read_latest_observation_value(observation_id) data = ObservationValuesSchema().dump({ "observation_id": observation_id, "values": values }) return jsonify(data)
def get(self, zone, *args): """ --- summary: List sites in a climate zone description: List all sites that the user has access to within the climate zone. tags: - Sites - Climate Zones parameters: - zone responses: 200: description: A list of sites content: application/json: schema: type: array items: $ref: '#/components/schemas/SiteMetadata' 401: $ref: '#/components/responses/401-Unauthorized' """ storage = get_storage() sites = storage.list_sites_in_zone(zone.replace('+', ' ')) return jsonify(SiteResponseSchema(many=True).dump(sites))
def get(self, aggregate_id): """ --- summary: Get aggregate probabilistic forecasts. description: > Get metadata for all Probabilistic Forecasts associated with the aggregate that the user has access to. tags: - Aggregates parameters: - aggregate_id responses: 200: description: Successfully retrieved aggregate cdf forecast groups. content: application/json: schema: type: array items: $ref: '#/components/schemas/CDFForecastGroupMetadata' 401: $ref: '#/components/responses/401-Unauthorized' 404: $ref: '#/components/responses/404-NotFound' """ storage = get_storage() forecasts = storage.list_cdf_forecast_groups(aggregate_id=aggregate_id) return jsonify(CDFForecastGroupSchema(many=True).dump(forecasts))
def get(self, site_id, *args): """ --- summary: Get site forecasts description: > Get metadata for all forecasts associated with site that user has access to. tags: - Sites parameters: - site_id responses: 200: description: Successfully retrieved site forecasts content: application/json: schema: type: array items: $ref: '#/components/schemas/ForecastMetadata' 401: $ref: '#/components/responses/401-Unauthorized' 404: $ref: '#/components/responses/404-NotFound' """ storage = get_storage() forecasts = storage.list_forecasts(site_id=site_id) return jsonify(ForecastSchema(many=True).dump(forecasts))
def get(self, observation_id, *args): """ --- summary: Get dates where flag not present. description: | Get the dates where and Observation data is NOT flagged with the given flag. tags: - Observations parameters: - observation_id - start_time - end_time - flag - timezone responses: 200: description: Unflagged observation values retrieved successfully. content: application/json: schema: $ref: '#/components/schemas/ObservationUnflagged' 401: $ref: '#/components/responses/401-Unauthorized' 404: $ref: '#/components/responses/404-NotFound' """ errors = {} try: start, end = validate_start_end() except BadAPIRequest as err: errors = err.errors tz = request.args.get('timezone', 'UTC') flag = request.args.get('flag', None) if tz not in ALLOWED_TIMEZONES: errors['timezone'] = f'Unknown timezone {tz}' if flag is None: errors['flag'] = 'Must provide the flag parameter' else: try: int(flag) except ValueError: errors['flag'] = 'Flag must be an integer' else: if int(flag) > (2**16 - 1) or int(flag) < 0: errors['flag'] = ('Flag must be a 2 byte unsigned ' 'integer between 0 and 65535') if errors: raise BadAPIRequest(errors) storage = get_storage() out = { 'dates': storage.find_unflagged_observation_dates(observation_id, start, end, flag, tz), 'observation_id': observation_id } data = ObservationUnflaggedSchema().dump(out) return jsonify(data)
def get(self, observation_id, *args): """ --- summary: Get Observation data. description: Get the timeseries values from the Observation entry. tags: - Observations parameters: - observation_id - start_time - end_time - accepts responses: 200: description: Observation values retrieved successfully. content: application/json: schema: $ref: '#/components/schemas/ObservationValues' text/csv: schema: type: string example: |- timestamp,value,quality_flag 2018-10-29T12:00:00Z,32.93,0 2018-10-29T13:00:00Z,25.17,0 400: $ref: '#/components/responses/400-TimerangeTooLarge' 401: $ref: '#/components/responses/401-Unauthorized' 404: $ref: '#/components/responses/404-NotFound' """ start, end = validate_start_end() storage = get_storage() values = storage.read_observation_values(observation_id, start, end) accepts = request.accept_mimetypes.best_match( ['application/json', 'text/csv']) if accepts == 'application/json': data = ObservationValuesSchema().dump({ "observation_id": observation_id, "values": values }) return jsonify(data) else: meta_url = url_for('observations.metadata', observation_id=observation_id, _external=True) csv_header = f'# observation_id: {observation_id}\n# metadata: {meta_url}\n' # NOQA csv_values = values.to_csv(columns=['value', 'quality_flag'], index_label='timestamp', date_format='%Y%m%dT%H:%M:%S%z') csv_data = csv_header + csv_values response = make_response(csv_data, 200) response.mimetype = 'text/csv' return response
def post(self, site_id, *args): """ --- summary: Update Site metadata. tags: - Sites parameters: - site_id requestBody: description: >- JSON object of site metadata to update. If modeling parameters are to be updated, all parameters for a given tracking_type are required even if most values are the same to ensure proper validation. An empty object for modeling_parameters will have the effect of clearing all modeling parameters. required: True content: application/json: schema: $ref: '#/components/schemas/SiteUpdate' responses: 200: description: Site updated successfully content: application/json: schema: type: string format: uuid description: The uuid of the updated site. headers: Location: schema: type: string format: uri description: Url of the updated site. 400: $ref: '#/components/responses/400-BadRequest' 401: $ref: '#/components/responses/401-Unauthorized' 404: $ref: '#/components/responses/404-NotFound' """ data = request.get_json() try: changes = SiteUpdateSchema().load(data) except ValidationError as err: raise BadAPIRequest(err.messages) storage = get_storage() changes.update(changes.pop('modeling_parameters', {})) storage.update_site(site_id, **changes) response = make_response(site_id, 200) response.headers['Location'] = url_for('sites.single', site_id=site_id) return response
def post(self, observation_id, *args): """ --- summary: Update Observation metadata. tags: - Observations parameters: - observation_id requestBody: description: >- JSON object of observation metadata to update. If 'uncertainty' is explicitly set to null, the value will be cleared from the stored metadata. required: True content: application/json: schema: $ref: '#/components/schemas/ObservationUpdate' responses: 200: description: Observation updated successfully content: application/json: schema: type: string format: uuid description: The uuid of the updated observation. headers: Location: schema: type: string format: uri description: Url of the updated observation. 400: $ref: '#/components/responses/400-BadRequest' 401: $ref: '#/components/responses/401-Unauthorized' 404: $ref: '#/components/responses/404-NotFound' """ data = request.get_json() try: changes = ObservationUpdateSchema().load(data) except ValidationError as err: raise BadAPIRequest(err.messages) storage = get_storage() storage.update_observation(observation_id, **changes) response = make_response(observation_id, 200) response.headers['Location'] = url_for('observations.single', observation_id=observation_id) return response
def post(self, *args): """ --- summary: Create observation. tags: - Observations description: >- Create a new Observation by posting metadata. Note that POST requests to this endpoint without a trailing slash will result in a redirect response. requestBody: description: JSON representation of an observation. required: True content: application/json: schema: $ref: '#/components/schemas/ObservationDefinition' responses: 201: description: Observation created successfully content: application/json: schema: type: string format: uuid description: The uuid of the created observation. headers: Location: schema: type: string format: uri description: Url of the created observation. 400: $ref: '#/components/responses/400-BadRequest' 401: $ref: '#/components/responses/401-Unauthorized' 404: $ref: '#/components/responses/404-NotFound' """ data = request.get_json() try: observation = ObservationPostSchema().load(data) except ValidationError as err: raise BadAPIRequest(err.messages) storage = get_storage() observation_id = storage.store_observation(observation) response = make_response(observation_id, 201) response.headers['Location'] = url_for('observations.single', observation_id=observation_id) return response
def post(self, *args): """ --- summary: Create site tags: - Sites description: >- Create a new Site by posting metadata. Note that POST requests to this endpoint without a trailing slash will result in a redirect response. requestBody: description: JSON respresentation of an site. required: True content: application/json: schema: $ref: '#/components/schemas/SiteDefinition' responses: 201: description: Site created successfully content: application/json: schema: type: string format: uuid description: The uuid of the created site. headers: Location: schema: type: string format: uri description: Url of the created site. 400: $ref: '#/components/responses/400-BadRequest' 401: $ref: '#/components/responses/401-Unauthorized' 404: $ref: '#/components/responses/404-NotFound' """ data = request.get_json() try: site = SiteSchema().load(data) except ValidationError as err: return jsonify({'errors': err.messages}), 400 storage = get_storage() site_id = storage.store_site(site) response = make_response(site_id, 201) response.headers['Location'] = url_for('sites.single', site_id=site_id) return response
def post(self): """ --- summary: Create a new report. tags: - Reports requestBody: description: Metadata of the report to create. content: application/json: schema: $ref: '#/components/schemas/ReportMetadata' responses: 201: description: Report created successfully. content: application/json: schema: type: string format: uuid description: The uuid of the created report. headers: Location: schema: type: string format: uri description: Url of the created report. 400: $ref: '#/components/responses/400-BadRequest' 401: $ref: '#/components/responses/401-Unauthorized' 404: $ref: '#/components/responses/404-NotFound' """ report = request.get_json() # report already valid json, so just store that # if validates properly errs = ReportPostSchema().validate(report) if errs: raise BadAPIRequest(errs) storage = get_storage() report_id = storage.store_report(report) response = make_response(report_id, 201) response.headers['Location'] = url_for('reports.single', report_id=report_id) enqueue_report(report_id, request.url_root.rstrip('/')) return response
def delete(self, report_id): """ --- summary: Delete a report. tags: - Reports parameters: - report_id responses: 204: description: Deleted report successfully. 401: $ref: '#/components/responses/401-Unauthorized' 404: $ref: '#components/responses/404-NotFound' """ storage = get_storage() storage.delete_report(report_id) return '', 204
def delete(self, role_id): """ --- summary: Delete a Role parameters: - role_id tags: - Roles responses: 204: description: Role deleted successfully. 401: $ref: '#/components/responses/401-Unauthorized' 404: $ref: '#/components/responses/404-NotFound' """ storage = get_storage() storage.delete_role(role_id) return '', 204
def delete(self, aggregate_id, *args): """ --- summary: Delete aggregate. description: Delete an Aggregate, including its values and metadata. tags: - Aggregates parameters: - aggregate_id responses: 204: description: Aggregate deleted successfully. 401: $ref: '#/components/responses/401-Unauthorized' 404: $ref: '#/components/responses/404-NotFound' """ storage = get_storage() storage.delete_aggregate(aggregate_id) return '', 204
def delete(self, observation_id, *args): """ --- summary: Delete observation. description: Delete an Observation, including its values and metadata. tags: - Observations parameters: - observation_id responses: 204: description: Observation deleted successfully. 401: $ref: '#/components/responses/401-Unauthorized' 404: $ref: '#/components/responses/404-NotFound' """ storage = get_storage() storage.delete_observation(observation_id) return '', 204
def delete(self, role_id, permission_id): """ --- summary: Remove a permission from a role parameters: - role_id - permission_id tags: - Roles - Permissions responses: 204: description: Removed permission successfully. 401: $ref: '#/components/responses/401-Unauthorized' 404: $ref: '#/components/responses/404-NotFound' """ storage = get_storage() storage.remove_permission_from_role(role_id, permission_id) return '', 204
def delete(self, aggregate_id, observation_id, *args): """ --- summary: Delete an observation from an aggregate. description: Delete all instances of an observation from an aggregate. tags: - Aggregates parameters: - aggregate_id - observation_id responses: 204: description: Observation deleted from aggregate successfully. 401: $ref: '#/components/responses/401-Unauthorized' 404: $ref: '#/components/responses/404-NotFound' """ storage = get_storage() storage.delete_observation_from_aggregate(aggregate_id, observation_id) return '', 204
def post(self, user_id, role_id): """ --- summary: Add a Role to a User. parameters: - user_id - role_id tags: - Users - Roles responses: 204: description: Role Added Successfully. 401: $ref: '#/components/responses/401-Unauthorized' 404: $ref: '#/components/responses/404-NotFound' """ storage = get_storage() storage.add_role_to_user(user_id, role_id) return '', 204