Uploaded image for project: 'Data Management'
  1. Data Management
  2. DM-8836

Implement versioning in SQuaSH REST API

    Details

    • Type: Story
    • Status: Done
    • Resolution: Done
    • Fix Version/s: None
    • Component/s: None
    • Labels:
      None

      Description

      SQuaSH API is being used by different clients (e.g. the bokeh apps and SQRE Bot service) we really need a versioning scheme so that a client can look at the version number and know if it can integrate with it.

      Semantic version MAJOR.MINOR.PATCH seems appropriate. When the MAJOR number increments, it means that backward incompatible changes have been made. When MINOR increments, new functionality has been added that should be backward compatible. Finally, a change to PATCH means that bug fixes have been made to an existing functionality.

      We could have a semantic version for individual endpoints used by different services.

      Django REST Framework has a number of different versioning schemes. I would suggest using the AcceptHeaderVersioning scheme.
      http://www.django-rest-framework.org/api-guide/versioning/#acceptheaderversioning

        Attachments

          Issue Links

            Activity

            Hide
            afausti Angelo Fausti added a comment -

            This will be needed for the jointcal epic.

            We want validate_drp and jointcal working in SQUASH at the same time, this will require changes in the SQUASH API to be compatible with the new verification framework.

            Show
            afausti Angelo Fausti added a comment - This will be needed for the jointcal epic. We want validate_drp and jointcal working in SQUASH at the same time, this will require changes in the SQUASH API to be compatible with the new verification framework.
            Hide
            jhoblitt Joshua Hoblitt added a comment -

            Using semver seems reasonable but note that we don't necessarily have to use full x.y.z format. Just bumping the major version would be sufficient.

            Show
            jhoblitt Joshua Hoblitt added a comment - Using semver seems reasonable but note that we don't necessarily have to use full x.y.z format. Just bumping the major version would be sufficient.
            Hide
            jsick Jonathan Sick added a comment -

            Accept header sounds good. For the new lsst.verify framework, which is a SQUASH API client, should the new version be

            Accept: application/json; version=2.0
            

            and we'll assume the current API is "version=1.0"

            Show
            jsick Jonathan Sick added a comment - Accept header sounds good. For the new lsst.verify framework, which is a SQUASH API client, should the new version be Accept: application/json; version=2.0 and we'll assume the current API is "version=1.0"
            Hide
            jhoblitt Joshua Hoblitt added a comment -

            Per discussion on slack, I think folks are leaning (weakly) towards using an HTTP header to request the API version. Eg.

            https://developer.github.com/v3/#current-version

            Show
            jhoblitt Joshua Hoblitt added a comment - Per discussion on slack, I think folks are leaning (weakly) towards using an HTTP header to request the API version. Eg. https://developer.github.com/v3/#current-version
            Hide
            jhoblitt Joshua Hoblitt added a comment -

            Something to consider is if the version should be mandatory, and if not, what the default api version should be. My weak preference would be to default to the latest version if no header is present and to make a minor release of post-qa that adds the version.

            Show
            jhoblitt Joshua Hoblitt added a comment - Something to consider is if the version should be mandatory, and if not, what the default api version should be. My weak preference would be to default to the latest version if no header is present and to make a minor release of post-qa that adds the version.
            Hide
            afausti Angelo Fausti added a comment - - edited

            Revisiting this ticket during the SQuaSH RESTful API Flask implementation.

            Here's a good article regarding versioning of restful APIs. It covers three methods 1) adding the version to the URL 2) using custom headers and 3) using the Accept Header while the author does not favor anyone in particular from the reasons presented there I would prefer the third method. Also, see how Accept Headers are used in GitHub API.

            I did some investigation how to implement methods 1) and 3) in Flask.

            Method 1) can be implemented with blueprints to include the version prefix in the URLs (see comment below). I was interested on it after Miguel Grinberg's approach on how to support multiple API versions in Flask.

            Method 3) can be implemented with flask-accept package that provides a decorator that specifies which view should be used depending on the content of the Accept Header.

            Some things to note:

            • Despite having a [previous RESTful API implemented in DRFIhttps://squash-api.lsst.codes/] I decided that the current implementation  in Flask (DM-12194) will be the v1 (there's no point in reimplementing the previous of the API in Flask at this point)
            • The versio number will user use only the MAJOR number.
            • By default, all requests receive version v1 of the API (default), however in the docs we'll encourage the user to always include the version via the accept header, while not mandatory.
            Show
            afausti Angelo Fausti added a comment - - edited Revisiting this ticket during the SQuaSH RESTful API Flask implementation. Here's a good article regarding versioning of restful APIs . It covers three methods 1) adding the version to the URL 2) using custom headers and 3) using the Accept Header while the author does not favor anyone in particular from the reasons presented there I would prefer the third method. Also, see how Accept Headers are used in GitHub API . I did some investigation how to implement methods 1) and 3) in Flask. Method 1) can be implemented with blueprints  to include the version prefix in the URLs (see comment below). I was interested on it after  Miguel Grinberg's approach  on how to support multiple API versions in Flask. Method 3) can be implemented with flask-accept  package that provides a decorator that specifies which view should be used depending on the content of the Accept Header. Some things to note: Despite having a [previous RESTful API implemented in DRFIhttps://squash-api.lsst.codes/]  I decided that the current implementation  in Flask ( DM-12194 ) will be the v1 (there's no point in reimplementing the previous of the API in Flask at this point) The versio number will user use only the MAJOR number. By default, all requests receive version v1 of the API (default), however in the docs we'll encourage the user to always include the version via the accept header, while not mandatory.
            Hide
            afausti Angelo Fausti added a comment - - edited

            Example of routes in method 1) described above.

             

            $ flask list_routes
             
            View                           Methods                        URL
            ----                           -------                        ---
            flasgger.<lambda>              GET,OPTIONS,HEAD               /apidocs/index.html
            v1.measurementlist             GET,OPTIONS,HEAD               /v1/measurements
            v1.register                    POST,OPTIONS                   /v1/register
            v1.metriclist                  GET,POST,OPTIONS,HEAD          /v1/metrics
            v1.userlist                    GET,OPTIONS,HEAD               /v1/users
            v1.specificationlist           GET,POST,OPTIONS,HEAD          /v1/specs
            v1.job                         POST,OPTIONS                   /v1/job
            flasgger.apispec_1             GET,OPTIONS,HEAD               /apispec_1.json
            flasgger.apidocs               GET,OPTIONS,HEAD               /apidocs/
            _default_auth_request_handler  POST,OPTIONS                   /auth
            v1.root                        GET,OPTIONS,HEAD               /v1/
            v1.measurement                 GET,POST,OPTIONS,HEAD          /v1/measurement/<int:job_id>
            v1.jenkins                     GET,OPTIONS,HEAD               /v1/jenkins/<string:ci_id>
            v1.metric                      GET,HEAD,POST,OPTIONS,DELETE   /v1/metric/<string:name>
            v1.user                        DELETE,GET,OPTIONS,HEAD        /v1/user/<string:username>
            v1.specification               GET,HEAD,POST,OPTIONS,DELETE   /v1/spec/<string:name>
            v1.jobwitharg                  DELETE,GET,OPTIONS,HEAD        /v1/job/<int:job_id>
            flasgger.static                GET,OPTIONS,HEAD               /flasgger_static/<path:filename>
            static                         GET,OPTIONS,HEAD               /static/<path:filename>
            

            Show
            afausti Angelo Fausti added a comment - - edited Example of routes in method 1) described above.   $ flask list_routes   View Methods URL ---- ------- --- flasgger.<lambda> GET,OPTIONS,HEAD /apidocs/index.html v1.measurementlist GET,OPTIONS,HEAD /v1/measurements v1.register POST,OPTIONS /v1/register v1.metriclist GET,POST,OPTIONS,HEAD /v1/metrics v1.userlist GET,OPTIONS,HEAD /v1/users v1.specificationlist GET,POST,OPTIONS,HEAD /v1/specs v1.job POST,OPTIONS /v1/job flasgger.apispec_1 GET,OPTIONS,HEAD /apispec_1.json flasgger.apidocs GET,OPTIONS,HEAD /apidocs/ _default_auth_request_handler POST,OPTIONS /auth v1.root GET,OPTIONS,HEAD /v1/ v1.measurement GET,POST,OPTIONS,HEAD /v1/measurement/<int:job_id> v1.jenkins GET,OPTIONS,HEAD /v1/jenkins/<string:ci_id> v1.metric GET,HEAD,POST,OPTIONS,DELETE /v1/metric/<string:name> v1.user DELETE,GET,OPTIONS,HEAD /v1/user/<string:username> v1.specification GET,HEAD,POST,OPTIONS,DELETE /v1/spec/<string:name> v1.jobwitharg DELETE,GET,OPTIONS,HEAD /v1/job/<int:job_id> flasgger.static GET,OPTIONS,HEAD /flasgger_static/<path:filename> static GET,OPTIONS,HEAD /static/<path:filename>
            Hide
            afausti Angelo Fausti added a comment -

            I consider this investigation done. When the time comes to create a new API version either method 1) or 3) could be used. For now the default version of the new API is v1 and that is reflected in the docs and in the code.

            See https://github.com/lsst-sqre/squash-rest-api/pull/8

            Show
            afausti Angelo Fausti added a comment - I consider this investigation done. When the time comes to create a new API version either method 1) or 3) could be used. For now the default version of the new API is v1 and that is reflected in the docs and in the code. See  https://github.com/lsst-sqre/squash-rest-api/pull/8

              People

              • Assignee:
                afausti Angelo Fausti
                Reporter:
                afausti Angelo Fausti
                Watchers:
                Adam Thornton, Angelo Fausti, Frossie Economou, Jonathan Sick, Joshua Hoblitt
              • Votes:
                0 Vote for this issue
                Watchers:
                5 Start watching this issue

                Dates

                • Created:
                  Updated:
                  Resolved:

                  Summary Panel