Exemple #1
0
def housekeeping_aggregate(function):
    """Aggregated PATE Housekeeping data

    GET /api/housekeeping/<string:function>
    Allowed aggregate functions are: avg, sum, min, max and count.
    Query parameters:
    begin - PATE timestamp (Unix timestamp)
    end - PATE timestamp (Unix timestamp)
    fields - A comma separated list of fields to return
    API returns 200 OK and:
    {
        ...,
        "data" : {
            <fields according to query parameter 'fields'>
        },
        ...
    }

    Parameters 'begin' and 'end' are integers, although the 'timestamp' field they are compared to, is a decimal number. NOTE: This datetime format is placeholder, because instrument development has not formally specified the one used in the actual satellite. Internally, Python timestamp is used.

    Unlike in the above described API endpoint, these responses do not explicitly include primary key field ('timestamp'), because that would defeat the purpose of the aggregate functions.
    """
    log_request(request)
    try:
        from api.Housekeeping import Housekeeping
        if function.lower() not in ('avg', 'sum', 'min', 'max', 'count'):
            raise api.InvalidArgument(
                "Function '{}' is not supported!".format(function))
        return api.response(Housekeeping(request).get(function))
    except Exception as e:
        return api.exception_response(e)
Exemple #2
0
def housekeeping():
    """PATE Housekeeping data

    GET /api/housekeeping
    Query parameters:
    begin - PATE timestamp (Unix timestamp)
    end - PATE timestamp (Unix timestamp)
    fields - A comma separated list of fields to return
    API returns 200 OK and:
    {
        ...,
        "data" : [
            {
                <fields according to query parameter 'fields'>
            },
            ...
        ],
        ...
    }

    Parameters 'begin' and 'end' are integers, although the 'rotation' field they are compared to, is a decimal number. NOTE: This datetime format is placeholder, because instrument development has not formally specified the one used in the actual satellite. Internally, Python timestamp is used.

    A JSON list of objects is returned. Among object properties, primary key 'timestamp' is always included, regardless what 'fields' argument specifies. Data exceeding 7 days should not be requested. For more data, CSV services should be used."""
    log_request(request)
    try:
        from api.Housekeeping import Housekeeping
        return api.response(Housekeeping(request).get())
    except Exception as e:
        return api.exception_response(e)
Exemple #3
0
def hitcount_aggregate(function):
    """Aggregated classified PATE particle hits

    GET /api/hitcount/<string:function>
    Allowed aggregate functions are: avg, sum, min, max and count.
    Query parameters:
    begin - PATE timestamp (Unix timestamp)
    end - PATE timestamp (Unix timestamp)
    fields - A comma separated list of fields to return
    API returns 200 OK and:
    {
        ...,
        "data" : {
            <fields according to query parameter 'fields'>
        },
        ...
    }

    Data is logically grouped into full rotations, each identified by the timestamp when the rotation started. Information on rotational period or starting time of each sector is not available within data. It must be deciphered separately, if needed.

    Parameters 'begin' and 'end' are integers, although the 'rotation' field they are compared to, is a decimal number. NOTE: This datetime format is placeholder, because instrument development has not formally specified the one used in the actual satellite. Internally, Python timestamp is used.

    A JSON list containing a single object is returned. The identifier field ('timestamp') is never included, because that would defeat the purpose of the aggregate functions."""
    log_request(request)
    try:
        from api.HitCount import HitCount
        if function.lower() not in ('avg', 'sum', 'min', 'max', 'count'):
            raise api.InvalidArgument(
                "Function '{}' is not supported!".format(function))
        return api.response(HitCount(request).get(function))
    except Exception as e:
        return api.exception_response(e)
Exemple #4
0
def hitcount():
    """Classified PATE hit counters

    GET /api/hitcount
    Query parameters:
    begin - PATE timestamp (Unix timestamp)
    end - PATE timestamp (Unix timestamp)
    fields - A comma separated list of fields to return
    API returns 200 OK and:
    {
        ...,
        "data" : [
            {
                <fields according to query parameter 'fields'>
            },
            ...
        ],
        ...
    }


    Data is logically grouped into full rotations, each identified by the timestamp when the rotation started. Field/column descriptions are unavailable until they have been formally specified by instrument development.

    Parameters 'begin' and 'end' are integers, although the 'rotation' field they are compared to, is a decimal number. NOTE: This datetime format is placeholder, because instrument development has not formally specified the one used in the actual satellite. Internally, Python timestamp is used.

    A JSON list of objects is returned. Among object properties, primary key 'timestamp' is always included, regardless what 'fields' argument specifies. Data exceeding 7 days should not be requested. For more data, CSV services should be used."""
    log_request(request)
    try:
        from api.HitCount import HitCount
        return api.response(HitCount(request).get())
    except Exception as e:
        return api.exception_response(e)
Exemple #5
0
def psu():
    """Read PSU values.

    GET /api/psu
    No query parameters supported.
    API returns 200 OK and:
    {
        ...,
        "data" : {
            "power"             : ("OFF" | "ON"),
            "state"             : ("OK" | "OVER CURRENT"),
            "measured_current"  : (float),
            "measured_voltage"  : (float),
            "voltage_setting"   : (float),
            "current_limit"     : (float),
            "modified"          : (int)
        },
        ...
    }

    If the backend is not running, 404 Not Found is returned.
    """
    log_request(request)
    try:
        from api.PSU import PSU
        return api.response(PSU(request).get())
    except Exception as e:
        return api.exception_response(e)
Exemple #6
0
def api_file(ftype=None):
    """List of downloadable file, with optional type filtering. Allowed types are "vm" and "usb".
    
    GET /api/file
    GET /api/file/usb
    GET /api/file/vm
    Query parameters:
    (none implemented)
    API returns 200 OK and:
    {
        ...,
        "data" : [
            {
                TBA
            },
            ...
        ],
        ...
    }
    """
    log_request(request)
    try:
        if ftype not in (None, "usb", "vm"):
            raise api.InvalidArgument(f"Invalid type '{ftype}'!")
        from api.File import File
        return api.response(File().search(file_type=ftype,
                                          downloadable_to=sso.role))
    except Exception as e:
        return api.exception_response(e)
Exemple #7
0
def pulseheight():
    """Raw PATE pulse height data.
    
    GET /api/pulseheight
    Query parameters:
    begin - PATE timestamp (Unix timestamp)
    end - PATE timestamp (Unix timestamp)
    fields - A comma separated list of fields to return
    API returns 200 OK and:
    {
        ...,
        "data" : [
            {
                <fields according to query parameter 'fields'>
            },
            ...
        ],
        ...
    }
    """
    log_request(request)
    try:
        from api.PulseHeight import PulseHeight
        return api.response(PulseHeight(request).get())
    except Exception as e:
        return api.exception_response(e)
Exemple #8
0
def api_file_owned():
    """Return JSON listing of files owned by currently authenticated person. Slightly 'special' endpoint that accepts only GET method and no parameters of any kind. Data is returned based on the SSO session role. Specially created for Upload and Manage UI, to list user's files."""
    log_request(request)
    try:
        return api.response(api.File().search(owner=sso.uid or ''))
    except Exception as e:
        return api.exception_response(e)
Exemple #9
0
def api_file_schema():
    """Create data schema JSON for client FORM creation."""
    log_request(request)
    try:
        return api.response(api.File().schema())
    except Exception as e:
        return api.exception_response(e)
Exemple #10
0
def pulseheight_aggregate(function):
    """Aggregated raw PATE pulse height data.

    GET /api/pulseheight/<string:function>
    Allowed aggregate functions are: avg, sum, min, max and count.
    Query parameters:
    begin - PATE timestamp (Unix timestamp)
    end - PATE timestamp (Unix timestamp)
    fields - A comma separated list of fields to return
    API returns 200 OK and:
    {
        ...,
        "data" : [
            {
                <fields according to query parameter 'fields'>
            },
            ...
        ],
        ...
    }
    """
    log_request(request)
    try:
        from api.PulseHeight import PulseHeight
        if function.lower() not in ('avg', 'sum', 'min', 'max', 'count'):
            raise api.InvalidArgument(
                "Function '{}' is not supported!".format(function))
        return api.response(PulseHeight(request).get(function))
    except Exception as e:
        return api.exception_response(e)
Exemple #11
0
def register(register_id):
    """Not yet implemented"""
    log_request(request)
    try:
        raise api.NotImplemented()
    except Exception as e:
        return api.exception_response(e)
Exemple #12
0
def api_not_implemented(path=''):
    """Catch-all route for '/api*' access attempts that do not match any defined routes.
    "405 Method Not Allowed" JSON reply is returned."""
    log_request(request)
    try:
        raise api.MethodNotAllowed(
            "Requested API endpoint ('{}') does not exist!".format("/api/" +
                                                                   path))
    except Exception as e:
        return api.exception_response(e)
Exemple #13
0
def note():
    """Search or create note(s).

    GET /api/note
    Query parameters:
    begin - PATE timestamp (Unix timestamp)
    end - PATE timestamp (Unix timestamp)
    API responds with 200 OK and:
    {
        ...,
        "data" : [
            {
                "id"            : (int),
                "session_id"    : (int),
                "text"          : (str),
                "created"       : (int)
            },
            ...
        ],
        ...
    }

    POST /api/note
    No query parameters supported.
    Required payload:
    {
        "text" : (str)
    }
    API will respond with 200 OK and:
    {
        ...,
        "data" : {
            id" : (int)
        },
        ...
    }
    """
    log_request(request)
    try:
        from api.Note import Note
        note = Note(request)
        if request.method == 'POST':
            return api.response(note.create())
        elif request.method == 'GET':
            return api.response(note.search())
        else:
            # Should be impossible
            raise api.MethodNotAllowed(
                "'{}' method not allowed for '{}'".format(
                    request.method, request.url_rule))
    except Exception as e:
        return api.exception_response(e)
Exemple #14
0
def api_file_id(id):
    """Database table row endpoint. Retrieve (GET) or update (PUT) record."""
    log_request(request)
    try:
        if request.method == 'PUT':
            return api.response(api.File().update(id, request, sso.uid))
        elif request.method == 'GET':
            return api.response(api.File().fetch(id))
        else:
            raise api.MethodNotAllowed(
                f"Method {request.method} not supported for this endpoint.")
    except Exception as e:
        return api.exception_response(e)
Exemple #15
0
def show_flask_config():
    """Middleware (Flask application) configuration. Sensitive entries are
    censored."""
    log_request(request)
    try:
        cfg = {}
        for key in app.config:
            cfg[key] = app.config[key]
        # Censor sensitive values
        for key in cfg:
            if key in ('SECRET_KEY', 'MYSQL_DATABASE_PASSWORD'):
                cfg[key] = '<CENSORED>'
        return api.response((200, cfg))
    except Exception as e:
        return api.exception_response(e)
Exemple #16
0
def show_flask_config():
    """Middleware (Flask application) configuration. Sensitive entries are
    censored."""
    # Allow output only when debugging AND when the user is authenticated
    if not sso.is_authenticated or not app.debug:
        return api.response((404, {'error': 'Permission Denied'}))
    log_request(request)
    try:
        cfg = {}
        for key in app.config:
            cfg[key] = app.config[key]
        # Censor sensitive values
        for key in cfg:
            if key in ('SECRET_KEY', 'MYSQL_DATABASE_PASSWORD'):
                cfg[key] = '<CENSORED>'
        return api.response((200, cfg))
    except Exception as e:
        return api.exception_response(e)
Exemple #17
0
def psu_power():
    """Agilent power supply remote control.

    GET /api/psu/power
    No query parameters supported.
    Response returns:
    {
        ...,
        "data" : {
            "power": ["ON", "OFF"],
            "modified": (int)
        },
        ...
    }

    POST /api/psu/power
    No query parameters supported.
    Required payload:
    {
        "power" : ("ON" | "OFF")
    }
    API will respond with 202 Accepted and:
    {
        ...,
        "data" : {
            "command_id" : (int)
        },
        ...
    }
    """

    log_request(request)
    try:
        include = ['power', 'modified']
        if request.method == 'GET':
            from api.PSU import PSU
            return api.response(PSU(request).get(include))
        else:
            from api.Command import Command
            return api.response(Command(request).post("PSU", "SET POWER"))
    except Exception as e:
        return api.exception_response(e)
Exemple #18
0
def psu_current_limit():
    """Read or Set Current Limit Value from PSU.

    GET /api/psu/current/limit
    No query parameters supported.
    Response returns:
    {
        ...,
        "data" : {
            "current_limit" : (float),
            "modified"      : (int)
        },
        ...
    }

    POST /api/psu/current/limit
    Required payload:
    {
        "current_limit" : (float)
    }
    API will respond with 202 Accepted and:
    {
        ...,
        "data" : {
            "command_id" : (int)
        },
        ...
    }
    """
    log_request(request)
    try:
        include = ['current_limit', 'modified']
        if request.method == 'GET':
            from api.PSU import PSU
            return api.response(PSU(request).get(include))
        else:
            from api.Command import Command
            return api.response(
                Command(request).post("PSU", "SET CURRENT LIMIT"))
    except Exception as e:
        return api.exception_response(e)
Exemple #19
0
def psu_voltage():
    """Read or set PSU output voltage.

    GET /api/psu/voltage
    No query parameters supported.
    Response returns:
    {
        ...,
        "data" : {
            "measured_voltage"  : (float),
            "voltage_setting"   : (float),
            "modified"          : (int)
        },
        ...
    }

    POST /api/psu/voltage
    Required payload:
    {
        "voltage" : (float)
    }
    API will respond with 202 Accepted and:
    {
        ...,
        "data" : {
            "command_id" : (int)
        },
        ...
    }
    """
    log_request(request)
    try:
        include = ['measured_voltage', 'voltage_setting', 'modified']
        if request.method == 'GET':
            from api.PSU import PSU
            return api.response(PSU(request).get(include))
        else:
            from api.Command import Command
            return api.response(Command(request).post("PSU", "SET VOLTAGE"))
    except Exception as e:
        return api.exception_response(e)
Exemple #20
0
def psu_current():
    """Read PSU current.

    GET /api/psu/voltage
    No query parameters supported.
    Response returns:
    {
        ...,
        "data" : {
            "measured_current"  : (float),
            "current_limit"     : (float),
            "modified"          : (int)
        },
        ...
    }"""
    log_request(request)
    try:
        include = ['measured_current', 'current_limit', 'modified']
        from api.PSU import PSU
        return api.response(PSU(request).get(include))
    except Exception as e:
        return api.exception_response(e)
Exemple #21
0
def api_publish(id=None):
    """POST with uploaded filename will trigger prepublish procedure. For .OVA files this means reading the included .OVF file for characteristics of the virtual machine. A 'file' table row is inserted and the ID will be returned in the response JSON body { 'id': <int> }."""
    try:
        # Dummy response tuple REMOVE WHEN THIS ROUTE IS COMPLETED!
        response = (200, {
            'data': {
                '.role': f'{sso.role}',
                '.is_teacher': f'{str(sso.is_teacher)}',
                '.is_student': f'{str(sso.is_student)}',
                '.is_anonymous': f'{str(sso.is_anonymous)}',
                '.is_authenticated': f'{str(sso.is_authenticated)}'
            }
        })
        raise api.NotImplemented("Sorry! Not yet implemented!")

        if not sso.is_teacher:
            raise api.Unauthorized("Teacher privileges required")
        if not app.config.get('UPLOAD_FOLDER', None):
            raise api.InternalError(
                "Configuration parameter 'UPLOAD_FOLDER' is not set!")

        if request.method == 'GET':
            file = request.args.get('file', default=None, type=str)
            folder = app.config.get('UPLOAD_FOLDER')
            if not file:
                raise api.InvalidArgument(
                    "GET Request must provide 'file' URI variable")
            # Generate JSONForm for editing
            response = api.Publish(request).preprocess(folder + "/" + file,
                                                       sso.uid)
        elif request.method == 'POST':
            pass
    except Exception as e:
        return api.exception_response(e)
    else:
        # api.response(code: int, payload: dict) -> Flask.response_class
        return api.response(response)
Exemple #22
0
def usb_file():
    """List of USB disk -type downloadables (column "type" = "usb").
    
    GET /api/usb
    Query parameters:
    (none implemented)
    API returns 200 OK and:
    {
        ...,
        "data" : [
            {
                TBA
            },
            ...
        ],
        ...
    }
    """
    log_request(request)
    try:
        from api.File import File
        return api.response(File(request).search("usb"))
    except Exception as e:
        return api.exception_response(e)
Exemple #23
0
def note_by_id(timestamp):
    """Fetch operator note (identified by timestamp).
    
    GET /api/note/<int:timestamp>
    No query parameters supported.
    API responds with 200 OK and:
    {
        ...,
        "data" : {
            "id"            : (int),
            "session_id"    : (int),
            "text"          : (str),
            "created"       : (int)
        },
        ...
    }
    """
    log_request(request)
    try:
        from api.Note import Note
        note = Note(request)
        api.response(note.fetch(timestamp))
    except Exception as e:
        return api.exception_response(e)
Exemple #24
0
def api_doc():
    """JSON API Documentation.
    Generates API document from the available endpoints. This functionality
    relies on PEP 257 (https://www.python.org/dev/peps/pep-0257/) convention
    for docstrings and Flask micro framework route ('rule') mapping to
    generate basic information listing on all the available REST API functions.
    This call takes no arguments.
    
    GET /sys/api
    
    List of API endpoints is returned in JSON.
    
    GET /api.html
    
    The README.md from /api is prefixed to HTML content. List of API endpoints
    is included as a table."""
    def htmldoc(docstring):
        """Some HTML formatting for docstrings."""
        result = None
        if docstring:
            docstring = docstring.replace('<', '&lt;').replace('>', '&gt;')
            result = "<br/>".join(docstring.split('\n')) + "<br/>"
        return result

    try:
        log_request(request)
        eplist = []
        for rule in app.url_map.iter_rules():
            if rule.endpoint != 'static':
                allowed = [
                    method for method in rule.methods
                    if method not in ('HEAD', 'OPTIONS')
                ]
                methods = ','.join(allowed)

                eplist.append({
                    'service': rule.endpoint,
                    'methods': methods,
                    'endpoint': str(rule),
                    'doc': app.view_functions[rule.endpoint].__doc__
                })

        #
        # Sort eplist based on 'endpoint'
        #
        eplist = sorted(eplist, key=lambda k: k['endpoint'])

        if 'api.html' in request.url_rule.rule:
            try:
                from ext.markdown2 import markdown
                with open('api/README.md') as f:
                    readme = markdown(f.read(), extras=["tables"])
            except:
                app.logger.exception("Unable to process 'api/README.md'")
                readme = ''
            html = "<!DOCTYPE html><html><head><title>API Listing</title>"
            html += "<link rel='stylesheet' href='/css/api.css'>"
            # substitute for favicon
            html += "<link rel='icon' href='data:;base64,iVBORw0KGgo='>"
            html += "</head><body>"
            html += readme
            html += "<h2>List of Flask routes and Endpoints</h2>"
            html += "<table class='endpointTable'><tr><th>Service</th><th>Methods</th><th>Endpoint</th><th>Documentation</th></tr>"
            for row in eplist:
                html += "<tr><td>{}</td><td>{}</td><td>{}</td><td>{}</td></tr>" \
                        .format(
                            row['service'],
                            row['methods'],
                            row['endpoint'].replace('<', '&lt;').replace('>', '&gt;'),
                            htmldoc(row['doc'])
                        )
            html += "</table></body></html>"
            # Create Request object
            response = app.response_class(response=html,
                                          status=200,
                                          mimetype='text/html')
            return response
        else:
            return api.response((200, {'endpoints': eplist}))
    except Exception as e:
        return api.exception_response(e)