Git Product home page Git Product logo

aiida-restapi's Introduction

Build Status Coverage Status Docs status PyPI version

aiida-restapi

AiiDA REST API for data queries and workflow management.

Uses pydantic for models/validation and fastapi for the ASGI application. Serve e.g. using uvicorn.

Features

  • /users (GET/POST) and /users/<id> (GET) endpoints
  • Authentication via JSON web tokens (see test_auth.py for the flow; also works via interactive docs)
  • User pydantic model for validation
  • Automatic documentation at http://127.0.0.1:8000/docs
  • Full specification at http://127.0.0.1:8000/openapi.json

Installation

pip install aiida-restapi[auth]

Usage

# start rest api
uvicorn aiida_restapi:app

# start rest api and reload for changes (for development)
uvicorn aiida_restapi:app --reload

Examples

See the examples directory.

Development

git clone https://github.com/aiidateam/aiida-restapi .
cd aiida-restapi
pip install -e .[pre-commit,testing]  # install extra dependencies
pre-commit install  # install pre-commit hooks
pytest -v  # discover and run all tests

See the developer guide for more information.

License

MIT

Contact

[email protected] [email protected]

aiida-restapi's People

Contributors

chrisjsewell avatar flavianojs avatar ltalirz avatar ninadbhat avatar sphuber avatar superstar54 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

aiida-restapi's Issues

How does this compare to GraphQL

Having just decided to move forward with this repository and moved it into aiidateam, I'm now going to be fickle and ask "why are we using REST instead of GraphQL?"

From the (naturally biased) opinion of graphql site, this is why it is better than REST: https://www.howtographql.com/basics/1-graphql-is-the-better-rest/

Particularly the new /querybuilder endpoint feels like essentially a bespoke implementation of graphql, but lacking things like pagination, typing etc.

Note there has already been a trial version of this in https://github.com/dev-zero/aiida-graphql, using https://pypi.org/project/strawberry-graphql. At the time a drawback was that in only supported python 3.7+, but this is no longer an issue since aiida-core only supports this.

This is certainly not to say I am insisting we do move to graphql, but I think it's good to have some "due diligence" here to say we have considered it. Particularly now since we are entirely replacing the existing REST framework.

Here's some youtube vids (the best source of research πŸ˜†) with a more balanced look:

Essentially I think it comes down to: GraphQL can be more complex to implement on the backend, but can be more powerful on the frontend (for clients).
So, do we except the outcome being a less powerful API, but with the benefit of (probably) easier development/maintenance? (quite possibly)

Decide profile handling

To keep things simple, the aiida-restapi package started without explicit handling of AiiDA profiles (it simply uses the default profile of the AiiDA installation where it is running).

This is fine for the moment, but will likely need to be generalized once this API starts being used in production.
Note that the decision of how to implement profile handling may depend on how we integrate authentication with the AiiDA side #5 .

filter query parameters of old AiiDA REST API incompatible with OpenAPI/fastapi standard

It seems that, in general, the exact structure of the query string is not standardized.
For example, RFC3986 only mentions that

query components are often used to carry identifying information in the form of "key=value" pairs

Similar the latest RFC8820.

However, there is a W3C recommendation for encoding form data (application/x-www-form-urlencoded):

The name is separated from the value by = and name/value pairs are separated from each other by &

While the OpenAPI standard is not explicit on the name-value separator, all of the examples in the spec use =.

As pointed out by @chrisjsewell, fastapi relies on starlette which internally relies on urllib's parse_qsl function for parsing the query parameters. parse_qsl (part of the python standard library) also hardcodes the = separator.

This clashes with the filter operators from the aiida-core rest api, which allow e.g. for queries like

http://localhost:5000/api/v4/nodes?mtime>=2019-04-23

Incorrect input field specification for POST methods

Basically the fields you can return from an ORM entity (via QueryBuilder) are not the same as the fields available to create an ORM entity, e.g. here it suggests you can specify the id and user_id, which is not true:

image

equally the way the pydantic models are set up in models.py, IMO, feel a bit "off":

class Group(AiidaModel):
    """AiiDA Group model."""

    _orm_entity = orm.Group

    id: Optional[int] = Field(description="Id of the object")
    label: str = Field(description="Used to access the group. Must be unique.")
    type_string: Optional[str] = Field(description="Type of the group")
    user_id: Optional[str] = Field(description="Id of the user that created the node.")
    description: Optional[str] = Field(description="Short description of the group.")

Here, everything except for label is set as Optional, since this is the only required field to create a group. But this does not accurately describe what is stored in the database, and thus what you would return from a GET method, where things like id are not optional (i.e. it should never be None).

Finally, fields like user_id will be returned from e.g. QueryBuilder().append(Group, project=["**"]), because they are a field on the database table, but they will always be None when using from_orm, because they are not exposed as an attribute of the AiiDA ORM class.

FYI this is what I was talking about in #17 (comment) @ltalirz

Also note the /groups POST method is currently called create_user 😜

Add `PUT` method to processes

Currently, no PUT method is available to the processes endpoint.

User story

In AiiDAlab, we want to update the extra parameters of a ProcessNode. This can be done using AiiDA directly.

process.base.extras.set("builder_parameters", parameters)

For REST API, the PUT method is needed to do this.

Relationship to `aiida-optimade`

@CasperWA has implemented https://github.com/aiidateam/aiida-optimade, which is essentially a specialised aiida server for optimade (mainly focused on querying for StructureData).
We should think if there is anything we can "share", be it actual code or just general ideas.

FYI @CasperWA you may be interested to see I took Optimade's filter grammar as an initial starting point for a querybuilder one: https://github.com/aiidateam/aiida-restapi/blob/master/aiida_restapi/static/filter_grammar.lark

authorization levels

@chrisjsewell writes

Some possible levels of access:

  • Data retrieval (could have this allowed by default)
  • Sensitive data retrieval ??
  • Element (node, group, computer, etc) additions
  • Element deletions
  • Submitting processes

I personally think we can start just with keeping the routes that are open in the aiida-core REST API open, and protecting any functionality that modifies the DB.

Create a client library

Currently using the API requires a lot of boiler plate for the client. It would be great to at least have a streamlined library in Python.

migrate REST API test suite from aiida-core

This is something that should happen gradually once we've agreed on the general architecture.

It's important not to do this too late in order to make sure our design is capable of replicating the old REST API's behavior.

bubble up python exceptions to response?

While testing manually we realized that errors like forgetting to load the AiiDA profile are reported as an internal server errors

We may want to include this info in the response (although this may be a security concern)

Change package name

Now that I've snuck in the initial GraphQL implementation (#17) πŸ˜„, it's a bit of a misnomer to call this just restapi.
Possible names: aiida-webserver, aiida-webapi

@ltalirz @NinadBhat ?

document how to run with SSL certs

Probably something like

# create adhoc ssl cert
openssl req -x509 -newkey rsa:4096 -nodes -out cert.pem -keyout key.pem -days 365 
uvicorn --ssl-keyfile key.pem  --ssl-certfile cert.pem aiida_restapi:app

Improve error message when submitting a process

Currently when submitting a process and the inputs are incorrect (for example label is missing, which shouldn't really even be required), the error message is not very useful.
We simply get a

422 Unprocessable Entity
{'detail': [{'loc': ['body', 'label'], 'msg': 'field required', 'type': 'value_error.missing'}]}

It should ideally tell what data is missing.

integrate into `verdi` cli

Integrate running of the REST API into the verdi cli, essentially replacing the verdi restapi command.

Since we're using uvicorn here it's not quite clear to me whether this is straightforward; also this currently means removing access to the old restapi, i.e. we can only do this once we're at feature parity.

This is not blocking for any use cases and also does not affect architectural considerations; thus low priority.

infer models from ORM / AiiDA DB layer

Reimplementing the entity models (node, user, ...) from scratch is quite straightforward with pydantic, and probably the quickest way to start.

Ideally, at some point however we should try to infer the models, either from AiiDA's public ORM interface or from the underlying django/sqlalchemy DB models.

Note: An intermediate solution may be to embed the pydantic models in the ORM in some way in order to at least keep these two close together.

Migrate existing endpoints

and also the tests (see #4)

from a call to the current API:

{
  "data": {
    "available_endpoints": [
      "calcjobs        GET,HEAD,OPTIONS     /api/v4/calcjobs/<id>/input_files/",
      "calcjobs        GET,HEAD,OPTIONS     /api/v4/calcjobs/<id>/output_files/",
      "computers       GET,HEAD,OPTIONS     /api/v4/computers/",
      "computers       GET,HEAD,OPTIONS     /api/v4/computers/<id>/",
      "computers       GET,HEAD,OPTIONS     /api/v4/computers/page/",
      "computers       GET,HEAD,OPTIONS     /api/v4/computers/page/<int:page>/",
      "computers       GET,HEAD,OPTIONS     /api/v4/computers/projectable_properties/",
      "groups          GET,HEAD,OPTIONS     /api/v4/groups/",
      "groups          GET,HEAD,OPTIONS     /api/v4/groups/<id>/",
      "groups          GET,HEAD,OPTIONS     /api/v4/groups/page/",
      "groups          GET,HEAD,OPTIONS     /api/v4/groups/page/<int:page>/",
      "groups          GET,HEAD,OPTIONS     /api/v4/groups/projectable_properties/",
      "nodes           GET,HEAD,OPTIONS     /api/v4/nodes/",
      "nodes           GET,HEAD,OPTIONS     /api/v4/nodes/<id>/",
      "nodes           GET,HEAD,OPTIONS     /api/v4/nodes/<id>/contents/attributes/",
      "nodes           GET,HEAD,OPTIONS     /api/v4/nodes/<id>/contents/comments/",
      "nodes           GET,HEAD,OPTIONS     /api/v4/nodes/<id>/contents/derived_properties/",
      "nodes           GET,HEAD,OPTIONS     /api/v4/nodes/<id>/contents/extras/",
      "nodes           GET,HEAD,OPTIONS     /api/v4/nodes/<id>/download/",
      "nodes           GET,HEAD,OPTIONS     /api/v4/nodes/<id>/links/incoming/",
      "nodes           GET,HEAD,OPTIONS     /api/v4/nodes/<id>/links/incoming/page/",
      "nodes           GET,HEAD,OPTIONS     /api/v4/nodes/<id>/links/incoming/page/<int:page>/",
      "nodes           GET,HEAD,OPTIONS     /api/v4/nodes/<id>/links/outgoing/",
      "nodes           GET,HEAD,OPTIONS     /api/v4/nodes/<id>/links/outgoing/page/",
      "nodes           GET,HEAD,OPTIONS     /api/v4/nodes/<id>/links/outgoing/page/<int:page>/",
      "nodes           GET,HEAD,OPTIONS     /api/v4/nodes/<id>/links/tree/",
      "nodes           GET,HEAD,OPTIONS     /api/v4/nodes/<id>/repo/contents/",
      "nodes           GET,HEAD,OPTIONS     /api/v4/nodes/<id>/repo/list/",
      "nodes           GET,HEAD,OPTIONS     /api/v4/nodes/download_formats/",
      "nodes           GET,HEAD,OPTIONS     /api/v4/nodes/full_types/",
      "nodes           GET,HEAD,OPTIONS     /api/v4/nodes/full_types_count/",
      "nodes           GET,HEAD,OPTIONS     /api/v4/nodes/page/",
      "nodes           GET,HEAD,OPTIONS     /api/v4/nodes/page/<int:page>/",
      "nodes           GET,HEAD,OPTIONS     /api/v4/nodes/projectable_properties/",
      "nodes           GET,HEAD,OPTIONS     /api/v4/nodes/statistics/",
      "processes       GET,HEAD,OPTIONS     /api/v4/processes/<id>/report/",
      "processes       GET,HEAD,OPTIONS     /api/v4/processes/projectable_properties/",
      "querybuilder    GET,HEAD,POST,OPTIONS /api/v4/querybuilder/",
      "server          GET,HEAD,OPTIONS     /api/v4/",
      "server          GET,HEAD,OPTIONS     /api/v4/server/",
      "server          GET,HEAD,OPTIONS     /api/v4/server/endpoints/",
      "users           GET,HEAD,OPTIONS     /api/v4/users/",
      "users           GET,HEAD,OPTIONS     /api/v4/users/<id>/",
      "users           GET,HEAD,OPTIONS     /api/v4/users/page/",
      "users           GET,HEAD,OPTIONS     /api/v4/users/page/<int:page>/",
      "users           GET,HEAD,OPTIONS     /api/v4/users/projectable_properties/"
    ]
  },
  "method": "GET",
  "path": "/api/v4",
  "query_string": "",
  "resource_type": "Info",
  "url": "https://15.188.110.176:5000/api/v4",
  "url_root": "https://15.188.110.176:5000/"
}

decide where to store passwords

It might be ok to store the hashed passwords of AiiDA users in the AiiDA DB - but perhaps in a different table (a bit like what is done for the Computers an the Authinfo).

To discuss

Missing package errors when starting the REST API service

I installed the aiida-restapi package in a new python virtual environment. Then, I tried to start the REST API with uvicorn aiida_restapi:app command. I got the following error:

ModuleNotFoundError: No module named 'jose'

I installed manually this module with pip install python-jose.
The next error was:

ModuleNotFoundError: No module named 'passlib'

which I solved with pip install passlib.

The final error was:

RuntimeError: Form data requires "python-multipart" to be installed.

solved with pip install python-multipart. Then finally it worked.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.