9 Commits

Author SHA1 Message Date
AndreaDAmico
51b266bd2a Fixing PyPi publishing in main.yml
Change-Id: Iaef853a09755f41d875a3a426fe4d303baa2be73
2025-04-18 11:26:42 -04:00
AndreaDAmico
13a81e8f94 Defining PyPi publishing
Change-Id: I6412a016e592f461bdef9e8d34ccacf0b8f078f5
2025-04-18 10:41:05 -04:00
Renato Ambrosone
c30308eb92 FIX: api version is now consistent with release
Change-Id: I25582e7937ec912666e2ba48d5687ec3ae694883
2025-04-18 15:09:02 +02:00
Renato Ambrosone
7695db8674 Implemented unit tests for PathRequestService: invalid equipment, topology, and success case
Change-Id: If863b6d2458642a682f2e687501433045c2311c9
2025-04-11 13:14:02 +02:00
Renato Ambrosone
192bb265bd Added test for /path-request route with expected input and response
Change-Id: I1af159af39136416ea7a379e02296c8ef3b606bf
2025-04-10 20:10:37 +02:00
Renato Ambrosone
b9acee661c Implemented HTTPS by default and http flag option as argument
Change-Id: I1717da2aa4644cd73c224111e57f7c0ede9036df
2025-04-02 02:09:17 +02:00
Renato Ambrosone
b0bda64b39 FIX: correct command example
Change-Id: Idde1a192532d5a81fa8e3d7bff11c08a96eb0902
2025-03-27 11:26:55 +01:00
Renato Ambrosone
fbb3d1dc7a FIX: correct json example
Change-Id: Ic138d8bb3dc5147cd6fa0f446dbfe01528bee9be
2025-03-27 11:26:02 +01:00
Renato Ambrosone
2133ded1a8 FIX: Missing 'path-request' registration in app initialization
Change-Id: Ia1d842efc5a536e5e2ef98920194a8d05604ff3e
2025-03-26 18:07:37 +01:00
15 changed files with 7934 additions and 1119 deletions

View File

@@ -53,6 +53,45 @@ jobs:
- tox_env: docs
dnf_install: graphviz
release-build:
runs-on: ubuntu-latest
needs: build
if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') && github.repository_owner == 'Telecominfraproject' }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: build release distributions
run: |
python -m pip install build
python -m build
- name: upload windows dists
uses: actions/upload-artifact@v4
with:
name: release-dists
path: dist/
pypi-publish:
runs-on: ubuntu-latest
needs:
- release-build
permissions:
id-token: write
steps:
- name: Retrieve release distributions
uses: actions/download-artifact@v4
with:
name: release-dists
path: dist/
- name: Publish release distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
other-platforms:
name: Tests on other platforms
runs-on: ${{ matrix.os }}

View File

@@ -1,34 +1,14 @@
# GNPy API
[![Python versions](https://img.shields.io/pypi/pyversions/gnpy)](https://pypi.org/project/gnpy/)
REST API (experimental)
-----------------------
``gnpyapi`` provides an experimental api for requesting several paths at once. It is based on Flask server.
You can run it through command line or Docker.
.. code-block:: shell-session
$ gnpy-rest
.. code-block:: shell-session
$ docker run -p 8080:8080 -it emmanuelledelfour/gnpy-experimental:candi-1.1 gnpy-rest
When starting the api server will aks for an encryption/decryption key. This key i used to encrypt equipment file when using /api/v1/equipments endpoint.
This key is a Fernet key and can be generated this way:
.. code-block:: python
from cryptography.fernet import Fernet
Fernet.generate_key()
After typing the key, you can detach the container by typing ^P^Q.
After starting the api server, you can launch a request
.. code-block:: shell-session
$ curl -v -X POST -H "Content-Type: application/json" -d @<PATH_TO_JSON_REQUEST_FILE> https://localhost:8080/api/v1/path-computation -k
$ curl --location 'http://localhost:8080/api/v1/path-request' --header 'Content-Type: application/json' --data @gnpyapi/exampledata/planning_demand_example.json
TODO: api documentation, unit tests, real WSGI server with trusted certificates

View File

@@ -4,4 +4,9 @@
"""
from flask import Flask
API_VERSION = "/api/v0.1"
app = Flask(__name__)
import gnpyapi.core.route.path_request_route # noqa: E402
import gnpyapi.core.route.status_route # noqa: F401, E402

View File

@@ -1,5 +1,4 @@
# coding: utf-8
from pathlib import Path
from flask import request
@@ -7,14 +6,12 @@ from gnpyapi.core import app
from gnpyapi.core.exception.equipment_error import EquipmentError
from gnpyapi.core.exception.topology_error import TopologyError
from gnpyapi.core.service.path_request_service import PathRequestService
from gnpyapi.core import API_VERSION
PATH_COMPUTATION_BASE_PATH = '/api/v1/path-computation'
PATH_REQUEST_BASE_PATH = '/api/v1/path-request'
AUTODESIGN_PATH = PATH_COMPUTATION_BASE_PATH + '/<path_computation_id>/autodesign'
_examples_dir = Path(__file__).parent.parent.parent / 'example-data'
PATH_REQUEST_BASE_PATH = '/path-request'
@app.route(API_VERSION + PATH_REQUEST_BASE_PATH, methods=['POST'])
@app.route(PATH_REQUEST_BASE_PATH, methods=['POST'])
def path_request(path_request_service: PathRequestService):
data = request.json

View File

@@ -1,7 +1,8 @@
# coding: utf-8
from gnpyapi.core import app
from gnpyapi.core import API_VERSION
@app.route('/api/v1/status', methods=['GET'])
@app.route(API_VERSION + '/status', methods=['GET'])
def api_status():
return {"version": "v1", "status": "ok"}, 200
return {"version": "v0.1", "status": "ok"}, 200

File diff suppressed because it is too large Load Diff

View File

@@ -19,6 +19,7 @@ from gnpyapi.core.exception.equipment_error import EquipmentError
from gnpyapi.core.exception.exception_handler import bad_request_handler, common_error_handler
from gnpyapi.core.exception.path_computation_error import PathComputationError
from gnpyapi.core.exception.topology_error import TopologyError
import argparse
_logger = logging.getLogger(__name__)
@@ -52,13 +53,22 @@ def _init_app():
app.register_error_handler(error_code, common_error_handler)
def main():
def main(http: bool = False):
_init_logger()
_init_app()
FlaskInjector(app=app)
if http:
app.run(host='0.0.0.0', port=8080)
else:
app.run(host='0.0.0.0', port=8080, ssl_context='adhoc')
if __name__ == '__main__':
main()
parser = argparse.ArgumentParser(description="Rest API example")
parser.add_argument("--http", action="store_true", help="run server with http instead of https")
args = parser.parse_args()
main(http=args.http)

View File

@@ -1,9 +1,9 @@
[metadata]
name = gnpyapi
name = gnpy-api
description-file = README.md
description-content-type = text/markdown; variant=GFM
author = Telecom Infra Project
author-email = tbd
author-email = adamico@nec-labs.com
license = BSD-3-Clause
home-page = https://github.com/Telecominfraproject/oopt-gnpy-api
project_urls =
@@ -51,6 +51,7 @@ install_requires =
gnpy==2.12.1
flask>=1.1.2
Flask-Injector
pyopenssl==25.0.0
[options.extras_require]
tests =

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

View File

@@ -0,0 +1,55 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# @Author: Esther Le Rouzic
# @Date: 2025-02-03
import json
from pathlib import Path
import pytest
from gnpyapi.core.exception.equipment_error import EquipmentError
from gnpyapi.core.service.path_request_service import PathRequestService
from gnpyapi.core.exception.topology_error import TopologyError
TEST_DATA_DIR = Path(__file__).parent.parent / 'data'
TEST_REQ_DIR = TEST_DATA_DIR / 'req'
TEST_RES_DIR = TEST_DATA_DIR / 'res'
def read_json_file(path):
with open(path, "r") as file:
return json.load(file)
def test_path_request_success():
input_data = read_json_file(TEST_REQ_DIR / "planning_demand_example.json")
expected_response = read_json_file(TEST_RES_DIR / "planning_demand_res.json")
topology = input_data["gnpy-api:topology"]
equipment = input_data["gnpy-api:equipment"]
service = input_data["gnpy-api:service"]
result = PathRequestService.path_request(topology, equipment, service)
assert result == expected_response
def test_path_request_invalid_equipment():
input_data = read_json_file(TEST_REQ_DIR / "planning_demand_wrong_eqpt.json")
topology = input_data["gnpy-api:topology"]
equipment = input_data["gnpy-api:equipment"]
service = input_data["gnpy-api:service"]
with pytest.raises(EquipmentError) as exc:
PathRequestService.path_request(topology, equipment, service)
assert "invalid" in str(exc.value).lower()
assert "deltap" in str(exc.value).lower()
def test_path_request_invalid_topology():
input_data = read_json_file(TEST_REQ_DIR / "planning_demand_wrong_topology.json")
topology = input_data["gnpy-api:topology"]
equipment = input_data["gnpy-api:equipment"]
service = input_data["gnpy-api:service"]
with pytest.raises(TopologyError) as exc:
PathRequestService.path_request(topology, equipment, service)
assert "can not find" in str(exc.value).lower()

View File

@@ -2,21 +2,47 @@
# -*- coding: utf-8 -*-
# @Author: Esther Le Rouzic
# @Date: 2025-02-03
import json
from pathlib import Path
import subprocess
import pytest # noqa: F401
import pytest
from flask_injector import FlaskInjector
from gnpyapi.core import app
YANG_DIR = Path(__file__).parent.parent / 'gnpyapi' / 'yang'
SAMPLE_DIR = Path(__file__).parent.parent / 'samples'
TEST_DATA_DIR = Path(__file__).parent / 'data'
TEST_REQ_DIR = TEST_DATA_DIR / 'req'
TEST_RES_DIR = TEST_DATA_DIR / 'res'
def test_pyang():
"""Verify that yang models pss pyang
"""
res = subprocess.run(['pyang', '-f', 'tree', '--tree-line-length', '69',
'-p', YANG_DIR, YANG_DIR / 'gnpy-api@2021-01-06.yang'],
stdout=subprocess.PIPE, check=True)
if res.returncode != 0:
assert False, f'pyang failed: exit code {res.returncode}'
API_VERSION = '/api/v0.1'
def read_json_file(path):
with open(path, "r") as file:
return json.load(file)
@pytest.fixture
def client():
app.testing = True
FlaskInjector(app=app)
with app.test_client() as client:
yield client
def test_echo(client):
input_data = read_json_file(TEST_REQ_DIR / "planning_demand_example.json")
expected_response = read_json_file(TEST_RES_DIR / "planning_demand_res.json")
response = client.post(f"{API_VERSION}/path-request", json=input_data)
assert response.status_code == 201
assert response.get_json() == expected_response
def test_status(client):
response = client.get(f"{API_VERSION}/status")
assert response.status_code == 200
assert response.get_json() == {"version": "v0.1", "status": "ok"}