Skip to content
This repository was archived by the owner on Apr 16, 2024. It is now read-only.

Cherrypy to flask #63

Draft
wants to merge 20 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Virtual environment for testing
venv/

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
Expand Down
4 changes: 2 additions & 2 deletions LmCommon/common/api_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"""
from copy import copy
import csv
from http import HTTPStatus
import json
import os
import urllib
Expand All @@ -12,8 +13,7 @@

from LmCommon.common.lm_xml import fromstring, deserialize
from LmCommon.common.lmconstants import (
BISON, BisonQuery, DwcNames, GBIF, HTTPStatus, Idigbio, IdigbioQuery,
Itis, URL_ESCAPES, ENCODING)
BISON, BisonQuery, DwcNames, GBIF, Idigbio, IdigbioQuery, Itis, URL_ESCAPES, ENCODING)
from LmCommon.common.occ_parse import OccDataParser
from LmCommon.common.ready_file import ready_filename

Expand Down
33 changes: 18 additions & 15 deletions LmWebServer/common/lmconstants.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
"""This module contains constants used by the Lifemapper web services
"""
import os
import secrets

from LmServer.base.utilities import get_mjd_time_from_iso_8601
from LmServer.common.lmconstants import SESSION_DIR
from LmServer.common.localconstants import SCRATCH_PATH, APP_PATH
from LmWebServer.common.localconstants import PACKAGING_DIR

FALLBACK_SECRET_KEY = secrets.token_hex()

# CherryPy constants
SESSION_PATH = os.path.join(SCRATCH_PATH, SESSION_DIR)
SESSION_KEY = '_cp_username'
Expand Down Expand Up @@ -51,9 +54,9 @@ def boolify_parameter(param, default=True):
try:
# Try processing a string
str_val = param.lower().strip()
if str_val == 'false' or str_val == 'no':
if str_val in('false', 'f', 'no','n'):
return False
if str_val == 'true' or str_val == 'yes':
if str_val in ('true', 't', 'yes', 'y'):
return True
except Exception:
pass
Expand Down Expand Up @@ -93,7 +96,7 @@ def boolify_parameter(param, default=True):
},
'atom': {
QP_NAME_KEY: 'atom',
QP_PROCESS_KEY: lambda x: boolify_parameter(x, default=True) # Boolify, default is true
QP_PROCESS_KEY: lambda x: boolify_parameter(x, default=True)
},
'beforestatus': {
QP_NAME_KEY: 'before_status',
Expand Down Expand Up @@ -143,18 +146,18 @@ def boolify_parameter(param, default=True):
},
'detail': {
QP_NAME_KEY: 'detail',
QP_PROCESS_KEY: lambda x: boolify_parameter(x, default=False) # Boolify, default is false
QP_PROCESS_KEY: lambda x: boolify_parameter(x, default=False)
},
'displayname': {
QP_NAME_KEY: 'display_name'
},
'docalc': {
QP_NAME_KEY: 'do_calc',
QP_PROCESS_KEY: lambda x: boolify_parameter(x, default=False) # Boolify, default is false
QP_PROCESS_KEY: lambda x: boolify_parameter(x, default=False)
},
'domcpa': {
QP_NAME_KEY: 'do_mcpa',
QP_PROCESS_KEY: lambda x: boolify_parameter(x, default=False) # Boolify, default is false
QP_PROCESS_KEY: lambda x: boolify_parameter(x, default=False)
},
'envcode': {
QP_NAME_KEY: 'env_code'
Expand All @@ -175,7 +178,7 @@ def boolify_parameter(param, default=True):
},
'fillpoints': {
QP_NAME_KEY: 'fill_points',
QP_PROCESS_KEY: lambda x: boolify_parameter(x, default=False) # Boolify, default is false
QP_PROCESS_KEY: lambda x: boolify_parameter(x, default=False)
},
'format': {
# TODO: Forward to respFormat since format is reserved
Expand All @@ -190,7 +193,7 @@ def boolify_parameter(param, default=True):
},
'hasbranchlengths': {
QP_NAME_KEY: 'has_branch_lengths',
QP_PROCESS_KEY: lambda x: boolify_parameter(x, default=True) # Boolify, default is true
QP_PROCESS_KEY: lambda x: boolify_parameter(x, default=True)
},
'height': {
QP_NAME_KEY: 'height',
Expand All @@ -204,19 +207,19 @@ def boolify_parameter(param, default=True):
},
'includecsvs': {
QP_NAME_KEY: 'include_csvs',
QP_PROCESS_KEY: lambda x: boolify_parameter(x, default=False) # Boolify, default is false
QP_PROCESS_KEY: lambda x: boolify_parameter(x, default=False)
},
'includesdms': {
QP_NAME_KEY: 'include_sdms',
QP_PROCESS_KEY: lambda x: boolify_parameter(x, default=False) # Boolify, default is false
QP_PROCESS_KEY: lambda x: boolify_parameter(x, default=False)
},
'isbinary': {
QP_NAME_KEY: 'is_binary',
QP_PROCESS_KEY: lambda x: boolify_parameter(x, default=True) # Boolify, default is true
QP_PROCESS_KEY: lambda x: boolify_parameter(x, default=True)
},
'isultrametric': {
QP_NAME_KEY: 'is_ultrametric',
QP_PROCESS_KEY: lambda x: boolify_parameter(x, default=True) # Boolify, default is true
QP_PROCESS_KEY: lambda x: boolify_parameter(x, default=True)
},
'keyword': {
QP_NAME_KEY: 'keyword',
Expand All @@ -235,7 +238,7 @@ def boolify_parameter(param, default=True):
},
'limit': {
QP_NAME_KEY: 'limit',
QP_PROCESS_KEY: lambda x: max(1, int(x)) # Integer, minimum is one
QP_PROCESS_KEY: lambda x: max(1, int(x)) # min = 1
},
'map': {
QP_NAME_KEY: 'map_name'
Expand All @@ -258,7 +261,7 @@ def boolify_parameter(param, default=True):
},
'minimumnumberofpoints': {
QP_NAME_KEY: 'minimum_number_of_points',
QP_PROCESS_KEY: lambda x: max(1, int(x)) # Integer, minimum is one
QP_PROCESS_KEY: lambda x: max(1, int(x)) # min = 1
},
'numpermutations': {
QP_NAME_KEY: 'num_permutations',
Expand All @@ -273,7 +276,7 @@ def boolify_parameter(param, default=True):
},
'offset': {
QP_NAME_KEY: 'offset',
QP_PROCESS_KEY: lambda x: max(0, int(x)) # Integer, minimum is zero
QP_PROCESS_KEY: lambda x: max(0, int(x)) # min = 0
},
'pathbiogeoid': {
QP_NAME_KEY: 'path_biogeo_id'
Expand Down
148 changes: 148 additions & 0 deletions LmWebServer/flask_app/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
"""The module provides a base Lifemapper service class
"""
from flask import session
from flask_login._compat import text_type
import os

from LmCommon.common.lmconstants import DEFAULT_POST_USER
from LmServer.common.lmconstants import ARCHIVE_PATH
from LmServer.common.localconstants import PUBLIC_USER
from LmServer.common.log import WebLogger
from LmServer.common.lmuser import LMUser
from LmServer.db.borg_scribe import BorgScribe

# app = Flask(__name__)

class WebUser(LMUser):
"""Extends lmuser objects for flask-login"""

# ................................
def __init__(
self, user_id, email, password, is_encrypted=False, first_name=None, last_name=None,
institution=None, addr_1=None, addr_2=None, addr_3=None, phone=None, mod_time=None):
"""Constructor

Args:
user_id: user chosen unique id
email: EMail address of user
password: user chosen password
first_name: The first name of this user
last_name: The last name of this user
institution: institution of user (optional)
addr_1: Address, line 1, of user (optional)
addr_2: Address, line 2, of user (optional)
addr_3: Address, line 3, of user (optional)
phone: Phone number of user (optional)
mod_time: Last modification time of this object (optional)
"""
LMUser.__init__(
self, user_id, email, password, is_encrypted=is_encrypted, first_name=first_name,
last_name=last_name, institution=institution, addr_1=addr_1, addr_2=addr_2, addr_3=addr_3,
phone=phone, mod_time=mod_time)
self._authenticated = False
self._active = False

# ..........................
def is_authenticated(self):
return self._authenticated

# ..........................
def is_active(self):
if self.user_id in (PUBLIC_USER, DEFAULT_POST_USER):
return False
return True

# ..........................
def is_anonymous(self):
if self.user_id in (PUBLIC_USER, DEFAULT_POST_USER):
return True
return False

# ..........................
def get_id(self):
if self.user_id not in (PUBLIC_USER, DEFAULT_POST_USER):
try:
return text_type(self.user_id)
except AttributeError:
raise NotImplementedError('No `user_id` attribute - override `get_id`')
return



# .............................................................................
class LmService:
"""This is the base Lifemapper service object

This is the base Lifemapper service object that the services can inherit
from. It is responsible for getting a database connection and logger that
can be used for the service.
"""

# ..........................
def __init__(self):
"""Constructor

The constructor is only responsible for getting a logger, user and a
scribe instance for the service.
"""
log = WebLogger()
self.scribe = BorgScribe(log)
self.scribe.open_connections()
self.log = log

# ..........................
def get_user(self, user_id=None):
"""Gets the user id for the service call.

Gets the user id for the service call. If user_id is provided, try
that first. Then try the session and finally fall back to the
PUBLIC_USER

TODO: Save the username in the session
"""
if user_id is None:
self.get_user_id()
usr = self.scribe.find_user(user_id)
return usr

# ..........................
@classmethod
def get_user_id(cls, user_id=None):
"""Gets the lmuser for the service call.

Gets the user id for the service call. If urlUser is provided, try
that first. Then try the session and finally fall back to the
PUBLIC_USER

TODO: Save the username in the session
"""
# Check to see if we should use url user
if user_id is not None:
if user_id.lower() == 'public':
return PUBLIC_USER
if user_id.lower() == DEFAULT_POST_USER:
return DEFAULT_POST_USER
# Try to get the user from the session
try:
return session['username']
except Exception:
# Fall back to PUBLIC_USER
return PUBLIC_USER

# ................................
@classmethod
def get_user_dir(cls, user_id):
"""Get the user's workspace directory

Todo:
Change this to use something at a lower level. This is using the
same path construction as the getBoomPackage script
"""
return os.path.join(ARCHIVE_PATH, user_id, 'uploads', 'biogeo')

# ..........................
@staticmethod
def OPTIONS():
"""Common options request for all services (needed for CORS)
"""
return
43 changes: 43 additions & 0 deletions LmWebServer/flask_app/biotaphy_names.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""This module provides a wrapper around GBIF's names service for use in the Biotaphy web application"""
import werkzeug.exceptions as WEXC

from LmCommon.common.api_query import GbifAPI
from LmWebServer.flask_app.base import LmService
from LmWebServer.flask_tools.lm_format import lm_formatter


# .............................................................................
class GBIFTaxonService(LmService):
"""Class to get and filter results from GBIF name-matching service."""

# ................................
@lm_formatter
def get_gbif_results(self, names_obj):
"""Queries GBIF for accepted names matching the provided list of names

Args:
names_obj(dict): a JSON list of name strings to match
"""
if not isinstance(names_obj, list):
return WEXC.BadRequest('Name data must be a JSON list')

retval = []
for name in names_obj:
try:
gbif_resp = GbifAPI.get_accepted_names(name)[0]
except Exception as e:
self.log.error('Could not get accepted name from GBIF for name {}: {}'.format(name, e))
retval.append({
GbifAPI.SEARCH_NAME_KEY: name,
GbifAPI.ACCEPTED_NAME_KEY: None,
GbifAPI.TAXON_ID_KEY: None
})
else:
retval.append({
GbifAPI.SEARCH_NAME_KEY: name,
GbifAPI.ACCEPTED_NAME_KEY: gbif_resp[
GbifAPI.SPECIES_NAME_KEY],
GbifAPI.TAXON_ID_KEY: gbif_resp[
GbifAPI.SPECIES_KEY_KEY]
})
return retval
Loading