Opentrons / opentrons

Software for writing protocols and running them on the Opentrons Flex and Opentrons OT-2
https://opentrons.com
Apache License 2.0
423 stars 178 forks source link

feat: Create python REST API client? #7847

Open beniroquai opened 3 years ago

beniroquai commented 3 years ago

Overview

We were wondering if there exists a way to automatically generate a Python client for the opentrons fastAPI using tools such as this toolbox

Implementation details

We tried using the fast_api_client generator without success (so far).

Design

Control basic functions of the robot remotely with an automated way of creating the client.

Acceptance criteria

Having the ability to control the opentrons robot through the fastAPI from a remote python console.

amitlissack commented 3 years ago

This is definitely something we should look into. I believe that one of our engineers had some success with a Javascript client generator.

Have you tried alternative client generators? FastAPI generates an OpenAPI JSON file. OpenAPI used to be known as Swagger.

https://swagger.io/tools/swagger-codegen/ (i've had success with this one in the past) https://openapi-generator.tech/

amitlissack commented 3 years ago

I did successfully create a client using https://swagger.io/tools/swagger-codegen/ . Hopefully that works for you.

beniroquai commented 3 years ago

Thanks for the quick reply!

I tried this one:

but unfortunately, it didn't work and give s the following error:

USER:Downloads bene$ openapi-python-client generate --path openapi.yaml
Error(s) encountered while generating, client was not created

Failed to parse OpenAPI document

30 validation errors for OpenAPI
components -> schemas -> ComparisonStatus -> $ref
  field required (type=value_error.missing)
components -> schemas -> ComparisonStatus -> properties -> differenceVector -> $ref
  field required (type=value_error.missing)
components -> schemas -> ComparisonStatus -> properties -> differenceVector -> items
  value is not a valid dict (type=type_error.dict)
components -> schemas -> ComparisonStatus -> properties -> differenceVector -> items
  value is not a valid dict (type=type_error.dict)
components -> schemas -> ComparisonStatus -> properties -> thresholdVector -> $ref
  field required (type=value_error.missing)
components -> schemas -> ComparisonStatus -> properties -> thresholdVector -> items
  value is not a valid dict (type=type_error.dict)
components -> schemas -> ComparisonStatus -> properties -> thresholdVector -> items
  value is not a valid dict (type=type_error.dict)
components -> schemas -> InstrumentOffset -> $ref
  field required (type=value_error.missing)
components -> schemas -> InstrumentOffset -> properties -> single -> $ref
  field required (type=value_error.missing)
components -> schemas -> InstrumentOffset -> properties -> single -> items
  value is not a valid dict (type=type_error.dict)
components -> schemas -> InstrumentOffset -> properties -> single -> items
  value is not a valid dict (type=type_error.dict)
components -> schemas -> InstrumentOffset -> properties -> multi -> $ref
  field required (type=value_error.missing)
components -> schemas -> InstrumentOffset -> properties -> multi -> items
  value is not a valid dict (type=type_error.dict)
components -> schemas -> InstrumentOffset -> properties -> multi -> items
  value is not a valid dict (type=type_error.dict)
components -> schemas -> JogPosition -> $ref
  field required (type=value_error.missing)
components -> schemas -> JogPosition -> properties -> vector -> $ref
  field required (type=value_error.missing)
components -> schemas -> JogPosition -> properties -> vector -> items
  value is not a valid dict (type=type_error.dict)
components -> schemas -> JogPosition -> properties -> vector -> items
  value is not a valid dict (type=type_error.dict)
components -> schemas -> LoadLabwareResult -> $ref
  field required (type=value_error.missing)
components -> schemas -> LoadLabwareResult -> properties -> calibration -> $ref
  field required (type=value_error.missing)
components -> schemas -> LoadLabwareResult -> properties -> calibration -> items
  value is not a valid dict (type=type_error.dict)
components -> schemas -> LoadLabwareResult -> properties -> calibration -> items
  value is not a valid dict (type=type_error.dict)
components -> schemas -> OffsetData -> $ref
  field required (type=value_error.missing)
components -> schemas -> OffsetData -> properties -> value -> $ref
  field required (type=value_error.missing)
components -> schemas -> OffsetData -> properties -> value -> items
  value is not a valid dict (type=type_error.dict)
components -> schemas -> OffsetData -> properties -> value -> items
  value is not a valid dict (type=type_error.dict)
components -> schemas -> WellLocation -> $ref
  field required (type=value_error.missing)
components -> schemas -> WellLocation -> properties -> offset -> $ref
  field required (type=value_error.missing)
components -> schemas -> WellLocation -> properties -> offset -> items
  value is not a valid dict (type=type_error.dict)
components -> schemas -> WellLocation -> properties -> offset -> items
  value is not a valid dict (type=type_error.dict)

If you believe this was a mistake or this tool is missing a feature you need, please open an issue at https://github.com/triaxtec/openapi-python-client/issues/new/choose

Would be cool to play with it a bit!

I'll have a look into the links you sent. Thanks!

beniroquai commented 3 years ago

@amitlissack thanks for pointing me to the swagger-apigenerator. I tested it using the following line to generate the client from the openapi.json file: swagger-codegen generate -i ~/Downloads/openapi.json -l python -o ~/Downloads/opentrons_python_api/ and it failed. I know this is a bit off-topic, but can you roughly remember the steps to reproduce it? (I'm not a programming expert, sorry ;). What I did is: Downloading the openapi.json file from the opentrons and executed the line above, does this make any sense at all?

In case it's of interest, the output of the terminal is the following: dump.txt

beniroquai commented 3 years ago

I'm really not an expert, but the error which says "filename too long" is probably coming from the nested/recurseive definition of the schema, or?

image

Is the API (I'm using the latest) up to date? Are you planning to integrate more functionalities or is it possible to "unlock" more of the fastAPI functions from the swagger ?

amitlissack commented 3 years ago

@beniroquai The API you're using is probably up to date. We are always adding more API.

I used the web site at https://swagger.io/tools/swagger-codegen/ . While it did successfully complete the generation, the resulting code is not that helpful.

We will have to spend time on this.


In the meantime, can I offer an alternative to code generation? The OpenAPI spec is generated from python classes in our robot-server directory. You can import the robot_server project into your client and use request and our pydantic classes.

Here is an example using our /health endpoint.

import requests
from robot_server.health import models

r = requests.get("http://localhost:31950/health", headers={"Opentrons-Version": "2"})

model = models.Health(**r.json())

print(model.name, model.fw_version, model.board_revision)
beniroquai commented 3 years ago

Great! I'll have a look at it! :-)

beniroquai commented 3 years ago

May I have a follow-up question? The above example works, so I could make use of the API which is defined in openapi.json using the classes and get nice looking code instead of hacking around with customized python classes..right? That's already nice! Thanks!

amitlissack commented 3 years ago

That is correct. It's not ideal, but you can be confident that the models you're using are accurate since they are being used by the server.

Each route handler lives in some python file in the robot-server repo. In case you're not familiar with FastAPI, the argument(s) to the handler are the data in the request. The response model defined by the decorator is the response model.

Please feel free to reach out for more questions.

We'll need a more comprehensive and dev-friendly solution.

beniroquai commented 3 years ago

Brilliant! Thanks! I'll have a look.

I think the coolest "thing" to have would be a python client which mirrors the OT2 API on the opentrons to a remote computer with all states and protocol functions ;-)