An open API service indexing awesome lists of open source software.

https://github.com/antoine-dh/iop-rest-client-framework


https://github.com/antoine-dh/iop-rest-client-framework

intersystems-iris

Last synced: 5 months ago
JSON representation

Awesome Lists containing this project

README

          

# iop-rest-client-framework

This framework is a Proof of Concept that aims to simplify the integration of REST API clients inside IRIS Interoperability-enabled productions using Python ([IOP](https://github.com/grongierisc/interoperability-embedded-python)) with minimal code required

## Features

- Define your API client in an elegant and declarative syntax, inspired by libraries such as [Retrofit](https://square.github.io/retrofit/) or [FastAPI](https://fastapi.tiangolo.com/)
- Full interoperability with either native IRIS [EnsLib.HTTP.OutboundAdapter](https://docs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic.cls?LIBRARY=ENSLIB&CLASSNAME=EnsLib.HTTP.OutboundAdapter) or python [requests](https://requests.readthedocs.io/en/latest/) as your HTTP backend
- Automatically generate your dataclass models and API methods by providing an [OpenAPI specification](https://swagger.io/specification/)
- Generated code is made to be human-readable with minimal boilerplate code to be easily editable if adjustments needs to be made

## Dependencies

- [iris-pex-embedded-python](https://github.com/grongierisc/interoperability-embedded-python) (IOP)
- [uplink](https://pypi.org/project/uplink/) - This project uses Uplink as a "frontend" for the API client definition
- [datamodel-code-generator](https://pypi.org/project/datamodel-code-generator/) + [pydantic](https://pypi.org/project/pydantic/) - Used to generate Pydantic dataclasses from OpenAPI specs
- [prance](https://pypi.org/project/prance/) - Used to parse and validate OpenAPI specs

## Example

### api\.py

This is all the code you need to define your models and API endpoints

```python
from iop_rest.api import BaseClientREST
from iop import Message
from pydantic import BaseModel
from uplink.arguments import *
from uplink.commands import *
from uplink.decorators import *
from uplink import returns

@dataclass(init=False)
class Pet(BaseModel, Message):
id: int
name: str

@returns.json
@headers({
"Content-Type": "application/json",
"Accept": "application/json",
"Charset": "utf-8",
})
class PetstoreClient(BaseClientREST):

@json
@post('pet')
def add_pet(self, body: Body(type=Pet)) -> Pet:
pass

@json
@get('pet/{petId}')
def get_pet_by_id(self, pet_id: Path("petId", type=int)) -> Pet:
pass
```

### bo\.py

Simple Business Operation that uses a EnsLib.HTTP.OutboundAdapter by default and passes it to our HTTP client

```python
from iop_rest.bo import BaseRESTOperation
from api import PetstoreClient
# from uplink import RequestsClient

class PetstoreOperation(BaseRESTOperation):
client: PetstoreClient

def init_client(self, adapter):
return PetstoreClient(adapter)
# Alternatively use python requests as the HTTP backend
# but lose the capabilities of the adapter
# return PetstoreClient(adapter, client=RequestsClient())
```

### bs\.py

```python
from iop import BusinessService
from iop_rest.messages import RESTRequest
from api import Pet

class PetstoreService(BusinessService):
@staticmethod
def get_adapter_type():
return "Ens.InboundAdapter"

def on_process_input(self, message_input):
pet = Pet(
id=1,
name="Robert",
)
response: Pet = self.send_request_sync('PetstoreOperation', RESTRequest.create('add_pet', pet))
# do something with response...
self.log_info(response.name)

```

## Demo

The demo can be found inside `src/python/demo/` and is a full implementation of the [Petstore OpenAPI spec](https://petstore3.swagger.io/) based on the above example

### Presentation video

[See the demo in action on Youtube](https://www.youtube.com/watch?v=MtK2eNVG0Gc)
*(Please enable subtitles)*

- The `bs.py` file is a simple BusinessService that is used to call periodically the Petstore BusinessOperation with a sample Pet object

- The `bo.py` file is a simple Business Operation that uses a native IRIS EnsLib.HTTP.OutboundAdapter by default and passes it to our HTTP client
- You can alternatively choose to use python's requests library as the HTTP backend, however you will lose capabilities offered by IRIS's HTTP OutboundAdapter

- The `generated/api.py` file contains the API definition generated by the iop_rest module, the generated code uses the [uplink](https://uplink.readthedocs.io/en/stable/) library at its core, and provides numerous decorators and type-checking abilities to allow for a very elegant and declarative syntax

- The `generated/models.py` file contains the dataclass models generated by the iop_rest module, it uses pydantic's base class to allow for automatic json serialization and deserialization by the uplink library

- The `settings.py` file contains the production definition and settings, see the [IOP documentation](https://github.com/grongierisc/interoperability-embedded-python?tab=readme-ov-file#61241-settingpy-file) for more information

## Prerequisites

Make sure you have [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) and [Docker](https://www.docker.com/products/docker-desktop) installed.

## Installation

### With Docker

Clone/git pull the repo into any local directory

```sh
git clone https://github.com/Antoine-dh/iop-rest-client-framework
```

Open the terminal in this directory and run:

```sh
docker compose build
```

Run the IRIS container with your project:

```sh
docker compose up -d
```

### With ZPM

```objectscript
zpm "install iop-rest-client-framework"
```

## Usage

### Code generation

If running Docker, open a bash terminal inside the container
```sh
docker compose exec iris bash
```

otherwise, make sure the iop_rest module is installed or accessible by the python interpreter (or just `cd src/python` before running the command)

To run the code generation program, use the following command

```sh
python -m iop_rest
```

For this demo:

```sh
python -m iop_rest PetstoreClient ./misc/petstore.openapi.yaml ./src/python/demo/generated/
```

## Issues and technical limitations

Keep in mind this is a proof of concept, and not a finished product, do not use for production.

- For now, only JSON APIs and UTF-8 charset is supported
- Exceptions are not currently handled properly
- Endpoints that returns JSON lists at the root of the HTTP body are not supported, this is due to a technical limitation with IOP messages that needs to be wrapped into a singular class, see [issue](https://github.com/grongierisc/interoperability-embedded-python/issues/25)
- Models' schemas inside OpenAPI specs are expected to be defined in `#/components/schemas/`, and responses to be `$ref`s to thoses schemas