Skip to content
Snippets Groups Projects
Commit 31fb64b9 authored by Martin Welss's avatar Martin Welss
Browse files

initial commit

parents
No related branches found
No related tags found
No related merge requests found
Showing
with 1268 additions and 0 deletions
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
.idea/
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
\ No newline at end of file
File added
File added
# AI4EU Experiments DSC Connection using gRPC
This is a simple implementation of the DSC (found here: https://github.com/International-Data-Spaces-Association/DataspaceConnector). For more information about the international dataspaces project plese consult https://internationaldataspaces.org/.
The AI4EU Node described here can be configured to download some text data with one DSC from another DSC. To use the node one has to configure it via the provided REST-api so the node knows which data to download and where to find it. After that the node gives this data to the next node and the pipeline can continue.
## Limitations
Because this is just a simple proof of concept there are known limitations which we want to adress here.
### Authentication
By now the Node can't be configured with authentication data for the used DSC. It always uses the default (username: admin, password: password).
### grpc configuration
At the moment the configuration can only be done by the provided REST-api. To better integrate the node into the whole grpc flow of AI4EU this configuration could also be done with a grpc service.
FROM maven:3-jdk-11 As dataspace-connector
WORKDIR /app
RUN curl -LO https://github.com/International-Data-Spaces-Association/DataspaceConnector/archive/refs/tags/v6.5.0.zip
RUN unzip v6.5.0.zip
WORKDIR /app/DataspaceConnector-6.5.0
RUN mvn -e -B dependency:resolve
RUN mvn -e -B dependency:resolve-plugins
RUN mvn -e -B clean package -DskipTests
FROM openjdk:11
WORKDIR /app
RUN apt-get update && apt-get install -y \
python3.9 \
python3-pip
COPY requirements.txt /app/requirements.txt
RUN pip install -r requirements.txt
COPY model.proto /app/model.proto
RUN python3 -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. model.proto
COPY src /app/src/
COPY static /app/static/
COPY templates /app/templates/
COPY app.py /app/app.py
COPY run.sh /app/run.sh
COPY --from=dataspace-connector /app/DataspaceConnector-6.5.0/target/*.jar /app/dataspaceconnector.jar
RUN useradd app
USER app
ENTRYPOINT ["/app/run.sh"]
\ No newline at end of file
# AI4EU Experiments DSC Connection using gRPC
This tutorial provides a basic Python programmer’s introduction to working with gRPC.
By walking through this example you’ll learn how to:
* Define a service in a .proto file.
* Generate server and client code using the protocol buffer compiler.
* Use the Python gRPC API to write a simple client and server for your service.
It assumes that you have read the Overview(https://grpc.io/docs/guides/#overview) and are familiar with protocol buffers.(https://developers.google.com/protocol-buffers/docs/overview)
You can find out more in the proto3 language guide and Python generated code guide.
(https://developers.google.com/protocol-buffers/docs/reference/python-generated)
# What is gRPC?
With gRPC you can define your service once in a .proto file and implement clients and
servers in any of gRPC’s supported languages, which in turn can be run in
environments ranging from servers inside Google to your own tablet - all the
complexity of communication between different languages and environments is
handled for you by gRPC. You also get all the advantages of working with protocol
buffers, including efficient serialization, a simple IDL, and easy interface updating.
This example is a Machine Learning Regression example that lets clients get the house
sales prediction based on chosen attributes.
# Steps
1. Write the service
2. Make a proto file
3. Generate gRPC classes for Python
4. Creating the Server
5. Write the web-server
6. Creating the Client
7. Include a license File
8. Prepare the Docker file
## Step 1: Write the Service
In our case, the service is to load the Data. Below is the code snippet. To better use the DSC we use the connectorAPI from https://github.com/International-Data-Spaces-Association/DataspaceConnector which also is provided in this repository
```python
from src.connectorAPI.idsapi import IdsApi
from src.connectorAPI.resourceapi import ResourceApi
def get_text(conf):
consumer_url = conf.custom_dsc if conf.use_custom_dsc else "https://localhost:8080"
consumer = IdsApi(consumer_url)
response = consumer.contractRequest(
conf.provider_url_downloading, conf.resource_id, conf.artifact_id, False, conf.contract
)
agreement = response["_links"]["self"]["href"]
consumer_resources = ResourceApi(consumer_url)
artifacts = consumer_resources.get_artifacts_for_agreement(agreement)
first_artifact = artifacts["_embedded"]["artifacts"][0]["_links"]["self"]["href"]
data = consumer_resources.get_data(first_artifact).text
print(data)
return data
```
We pass a conf object to the service which holds the information about which data should be downloaded. For better encapsulation of this information, we use the below configuration class as a singleton.
```python
class Configuration:
def __new__(cls):
if not hasattr(cls, 'instance'):
cls.instance = super(Configuration, cls).__new__(cls)
return cls.instance
recipient = None
resource_id = None
artifact_id = None
contract = None
custom_dsc = None
use_custom_dsc = False
data_send = False
```
## Step 2: Make the Proto File
To implement the service as a gRPC service, we need to describe its interface in our model.proto file first
```proto
syntax = 'proto3';
message Text {
string text = 1;
}
message Empty {
}
service IDSTextConnector {
rpc get_text(Empty) returns(Text);
}
```
## Step 3: Generate gRPC classes for Python
Open the terminal, change the directory to be in the same folder where the proto file is
in.
To generate the gRPC classes, we have to install the following needed libraries first:
* Install gRPC :
```cmd
python3 -m pip install grpcio
```
* To install gRPC tools, run:
```commandline
python3 -m pip install grpcio-tools googleapis-common-protos
```
* Now, run the following command:
```commandline
python3 -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. model.proto
```
This command used model.proto file to generate the needed stubs to create the
client/server.
The files generated will be as follows:
model_pb2.py — contains message classes
* model_pb2.Features for the input features
* model_pb2.Prediction for the prediction price
model_pb2_grpc.py — contains server and client classes
* model_pb2_grpc.PredictServicer will be used by the server
* model_pb2_grpc.PredictStub the client will use it
## Step 4: Creating the Server
The server will import the generated files and the function that will handle the
predictions. Then we will define a class that will take a request from the client and
uses the prediction function to return a respond. The request gives us the five
features, the response is a prediction.
After that, we will use add_PredictServicer_to_server function from (model_pb2_grpc.py)
file that was generated before to add the class PredictSevicer to the server.
Once you have implemented all the methods, the next step is to start up a gRPC
server so that clients can actually use your service.
The gRPC server is expected to run on port 8061
The optional HTTP-Server for a Web-UI for human interaction is expected to run on
port 8062.
Below is the house_sale_prediction_client.py
```python
import grpc
from concurrent import futures
import model_pb2
import model_pb2_grpc
import src.dsc_grpc_service as dgs
from src.state.configuration_state import Configuration
class TextServicer(model_pb2_grpc.IDSTextConnectorServicer):
conf:Configuration = None
def get_text(self, request, context):
response = model_pb2.Text()
if self.conf.data_send:
self.conf.data_send = False
context.set_code(grpc.StatusCode.NOT_FOUND)
context.set_details('All available data has been processed')
else:
self.conf.data_send = True
response.text = dgs.get_text(self.conf)
return response
def start_server(port: int, conf):
print("starting grpc server")
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
servicer = TextServicer()
servicer.conf = conf
model_pb2_grpc.add_IDSTextConnectorServicer_to_server(servicer, server)
print("Starting model_grpc Server. Listening on port : " + str(port))
server.add_insecure_port("[::]:{}".format(port))
server.start()
return server
```
## Step 5: Write the web-server
To Configure the Node with all its needed Data, we write a REST api, that listens on the same Port as the web-ui. This is just a proof of concept and the configuration could be done in other ways. For example, we could also write a grpc service to implement the configuration directly into the pipeline.
```python
import os.path
import pprint
from flask import Flask, render_template, request
from flask_bootstrap import Bootstrap
from flask_marshmallow import Marshmallow
from flask_wtf import FlaskForm
from wtforms import StringField, BooleanField, SubmitField
from wtforms.validators import DataRequired
from src.dsc_grpc_service import get_text
from src.state.configuration_state import Configuration, get_jsonifyed_configuration
template_dir = os.path.abspath('templates')
app = Flask(__name__, template_folder=template_dir)
ma = Marshmallow(app)
conf = None
class HppInputForm(FlaskForm):
recipient_str = StringField('Recipient', validators=[DataRequired(), ])
resource_id_str = StringField('Resource Id', validators=[DataRequired(), ])
artifact_id_str = StringField('Artifact Id', validators=[DataRequired(), ])
contract_input_str = StringField('Contract', validators=[DataRequired(), ])
custom_consumer_toggle = BooleanField('Use Custom Consumer')
custom_consumer_str = StringField('Custom Consumer')
submit = SubmitField('Submit Configuration')
class PullForm(FlaskForm):
submit = SubmitField('Pull Data')
@app.route('/api/v1/recipient', methods=["POST"])
def set_recipient():
conf.recipient = request.get_json()['recipient']
pprint.pprint(conf.recipient)
return get_jsonifyed_configuration()
@app.route('/api/v1/resourceId', methods=["POST"])
def set_resource_id():
conf.resource_id = request.get_json()['resourceId']
return get_jsonifyed_configuration()
@app.route('/api/v1/artifactId', methods=["POST"])
def set_artifact_id():
conf.artifact_id = request.get_json()['artifactId']
return get_jsonifyed_configuration()
@app.route('/api/v1/download', methods=["POST"])
def set_download():
conf.download = request.get_json()['download']
return get_jsonifyed_configuration()
@app.route('/api/v1/contract', methods=["POST"])
def set_contract():
conf.contract = request.get_json()['contract']
return get_jsonifyed_configuration()
@app.route('/api/v1/useCustomDSC', methods=["POST"])
def set_use_custom_dsc():
conf.use_custom_dsc = request.get_json()['useCustomDSC']
return get_jsonifyed_configuration()
@app.route('/api/v1/data', methods=["GET"])
def get_data():
data = get_text(conf)
return data
@app.route('/api/v1/customDSC', methods=["POST"])
def set_custom_dsc():
conf.custom_dsc = request.get_json()['customDSC']
return get_jsonifyed_configuration()
@app.route('/', methods=["GET", "POST"])
@app.route('/hpp_input', methods=["GET", "POST"])
def index():
form = HppInputForm()
pull_form = PullForm()
if pull_form.submit.data and pull_form.validate_on_submit():
return render_template("index.html", form=form, data=get_text(conf), current_configuration=Configuration(),
pull_form=pull_form)
if form.submit.data and form.validate_on_submit():
print('processing User Input')
conf.recipient = form.recipient_str.data
conf.resource_id = form.resource_id_str.data
conf.artifact_id = form.artifact_id_str.data
conf.contract = form.contract_input_str.data
conf.use_custom_dsc = form.custom_consumer_toggle.data
conf.custom_dsc = form.custom_consumer_str.data
return render_template("index.html", form=form, current_configuration=Configuration(), pull_form=pull_form)
return render_template('index.html', form=form, current_configuration=Configuration(), pull_form=pull_form)
def run_flask_app(host: str, port: int, p_conf):
global conf
conf = p_conf
app.secret_key = "dscmodel"
bootstrap = Bootstrap(app)
app.app_context().push()
app.run(host=host, port=port)
```
## Step 6: Creating the Client
For the implementation of a test client pleas have a look at the test-scripts folder.
## Step 7: Include a license File
We need to include a license file before building a docker image.
## Step 8: Prepare the Docker file
In the Dockerfile, we download the DataspaceConnector version 6.5.0 and build it. This Dataspace Connector is used by default, if no other is provided as consumer. To run the DSC we then use the openjdk:11 where we install the needed Python version.
```dockerfile
FROM maven:3-jdk-11 As dataspace-connector
WORKDIR /app
RUN curl -LO https://github.com/International-Data-Spaces-Association/DataspaceConnector/archive/refs/tags/v6.5.0.zip
RUN unzip v6.5.0.zip
WORKDIR /app/DataspaceConnector-6.5.0
RUN mvn -e -B dependency:resolve
RUN mvn -e -B dependency:resolve-plugins
RUN mvn -e -B clean package -DskipTests
FROM openjdk:11
WORKDIR /app
RUN apt-get update && apt-get install -y \
python3.9 \
python3-pip
COPY requirements.txt /app/requirements.txt
RUN pip install -r requirements.txt
COPY model.proto /app/model.proto
RUN python3 -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. model.proto
COPY src /app/src/
COPY static /app/static/
COPY templates /app/templates/
COPY app.py /app/app.py
COPY run.sh /app/run.sh
COPY --from=dataspace-connector /app/DataspaceConnector-6.5.0/target/*.jar /app/dataspaceconnector.jar
RUN useradd app
USER app
ENTRYPOINT ["/app/run.sh"]
```
The used requirements.txt:
```requirements.txt
Bootstrap-Flask==1.5.2
Flask==1.1.2
Flask-SQLAlchemy==2.5.1
Flask-WTF==0.14.3
flask-marshmallow==0.14.0
google==3.0.0
googleapis-common-protos==1.53.0
grpcio==1.38.0
grpcio-tools==1.38.0
Jinja2==2.11.3
protobuf==3.16.0
PyYAML==5.4.1
requests==2.25.1
SQLAlchemy==1.4.7
threadpoolctl==2.2.0
urllib3==1.26.5
Werkzeug==1.0.1
WTForms==2.3.3
```
The used run.sh:
```shell
#!/bin/bash
java -jar /app/dataspaceconnector.jar &
python3 -u app.py
```
Build the docker image
```commandline
docker build -t dsc-text-ai4eu:v1 .
```
Run the docker image
```commandline
docker run -p 8061:8061 --rm -ti dsc-text-ai4eu:v1 /bin/bash
```
The -p option maps the port on the container to the host.
The Docker run internally executes house_price_prediction_server.py.
Open one more terminal and run the client which now can access the docker server
```commandline
python3 house_price_prediction_client.py
```
import threading
from src import grpc_server
from src.flask_server import run_flask_app
import logging
from src.state.configuration_state import Configuration
grpc_port = 8061
flask_host = "0.0.0.0"
flask_port = 8062
conf = Configuration()
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
def main():
server = grpc_server.start_server(grpc_port, conf)
flask_server_thread = threading.Thread(target=run_flask_app, args=(flask_host, flask_port, conf,))
flask_server_thread.start()
server.wait_for_termination()
if __name__ == '__main__':
logging.basicConfig()
main()
syntax = 'proto3';
message Text {
string text = 1;
}
message Empty {
}
service IDSTextConnector {
rpc get_text(Empty) returns(Text);
}
\ No newline at end of file
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: model.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor.FileDescriptor(
name='model.proto',
package='',
syntax='proto3',
serialized_options=None,
create_key=_descriptor._internal_create_key,
serialized_pb=b'\n\x0bmodel.proto\"\x14\n\x04Text\x12\x0c\n\x04text\x18\x01 \x01(\t\"\x07\n\x05\x45mpty2-\n\x10IDSTextConnector\x12\x19\n\x08get_text\x12\x06.Empty\x1a\x05.Textb\x06proto3'
)
_TEXT = _descriptor.Descriptor(
name='Text',
full_name='Text',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='text', full_name='Text.text', index=0,
number=1, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=b"".decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=15,
serialized_end=35,
)
_EMPTY = _descriptor.Descriptor(
name='Empty',
full_name='Empty',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=37,
serialized_end=44,
)
DESCRIPTOR.message_types_by_name['Text'] = _TEXT
DESCRIPTOR.message_types_by_name['Empty'] = _EMPTY
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
Text = _reflection.GeneratedProtocolMessageType('Text', (_message.Message,), {
'DESCRIPTOR' : _TEXT,
'__module__' : 'model_pb2'
# @@protoc_insertion_point(class_scope:Text)
})
_sym_db.RegisterMessage(Text)
Empty = _reflection.GeneratedProtocolMessageType('Empty', (_message.Message,), {
'DESCRIPTOR' : _EMPTY,
'__module__' : 'model_pb2'
# @@protoc_insertion_point(class_scope:Empty)
})
_sym_db.RegisterMessage(Empty)
_IDSTEXTCONNECTOR = _descriptor.ServiceDescriptor(
name='IDSTextConnector',
full_name='IDSTextConnector',
file=DESCRIPTOR,
index=0,
serialized_options=None,
create_key=_descriptor._internal_create_key,
serialized_start=46,
serialized_end=91,
methods=[
_descriptor.MethodDescriptor(
name='get_text',
full_name='IDSTextConnector.get_text',
index=0,
containing_service=None,
input_type=_EMPTY,
output_type=_TEXT,
serialized_options=None,
create_key=_descriptor._internal_create_key,
),
])
_sym_db.RegisterServiceDescriptor(_IDSTEXTCONNECTOR)
DESCRIPTOR.services_by_name['IDSTextConnector'] = _IDSTEXTCONNECTOR
# @@protoc_insertion_point(module_scope)
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
"""Client and server classes corresponding to protobuf-defined services."""
import grpc
import model_pb2 as model__pb2
class IDSTextConnectorStub(object):
"""Missing associated documentation comment in .proto file."""
def __init__(self, channel):
"""Constructor.
Args:
channel: A grpc.Channel.
"""
self.get_text = channel.unary_unary(
'/IDSTextConnector/get_text',
request_serializer=model__pb2.Empty.SerializeToString,
response_deserializer=model__pb2.Text.FromString,
)
class IDSTextConnectorServicer(object):
"""Missing associated documentation comment in .proto file."""
def get_text(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def add_IDSTextConnectorServicer_to_server(servicer, server):
rpc_method_handlers = {
'get_text': grpc.unary_unary_rpc_method_handler(
servicer.get_text,
request_deserializer=model__pb2.Empty.FromString,
response_serializer=model__pb2.Text.SerializeToString,
),
}
generic_handler = grpc.method_handlers_generic_handler(
'IDSTextConnector', rpc_method_handlers)
server.add_generic_rpc_handlers((generic_handler,))
# This class is part of an EXPERIMENTAL API.
class IDSTextConnector(object):
"""Missing associated documentation comment in .proto file."""
@staticmethod
def get_text(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(request, target, '/IDSTextConnector/get_text',
model__pb2.Empty.SerializeToString,
model__pb2.Text.FromString,
options, channel_credentials,
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
Bootstrap-Flask==1.5.2
Flask==1.1.2
Flask-SQLAlchemy==2.5.1
Flask-WTF==0.14.3
flask-marshmallow==0.14.0
google==3.0.0
googleapis-common-protos==1.53.0
grpcio==1.38.0
grpcio-tools==1.38.0
Jinja2==2.11.3
protobuf==3.16.0
PyYAML==5.4.1
requests==2.25.1
SQLAlchemy==1.4.7
threadpoolctl==2.2.0
urllib3==1.26.5
Werkzeug==1.0.1
WTForms==2.3.3
\ No newline at end of file
#!/bin/bash
java -jar /app/dataspaceconnector.jar &
python3 -u app.py
\ No newline at end of file
#
# Copyright 2020 Fraunhofer Institute for Software and Systems Engineering
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import requests
import json
# Suppress ssl verification warning
requests.packages.urllib3.disable_warnings()
class IdsApi:
session = None
recipient = None
def __init__(self, recipient, auth=("admin", "password")):
self.session = requests.Session()
self.session.auth = auth
self.session.verify = False
self.recipient = recipient
def descriptionRequest(self, recipient, elementId):
url = self.recipient + "/api/ids/description"
params = {}
if recipient is not None:
params["recipient"] = recipient
if elementId is not None:
params["elementId"] = elementId
response = self.session.post(url, params=params)
return json.loads(response.text)
def contractRequest(self, recipient, resourceId, artifactId, download, contract):
url = self.recipient + "/api/ids/contract"
params = {}
if recipient is not None:
params["recipient"] = recipient
if resourceId is not None:
params["resourceIds"] = resourceId
if artifactId is not None:
params["artifactIds"] = artifactId
if download is not None:
params["download"] = download
response = self.session.post(
url, params=params, json=self.toListIfNeeded(contract)
)
return json.loads(response.text)
def toListIfNeeded(self, obj):
if isinstance(obj, list):
return obj
else:
return [obj]
#
# Copyright 2020 Fraunhofer Institute for Software and Systems Engineering
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import requests
import json
# Suppress ssl verification warning
requests.packages.urllib3.disable_warnings()
class ResourceApi:
session = None
recipient = None
def __init__(self, recipient, auth=("admin", "password")):
self.session = requests.Session()
self.session.auth = auth
self.session.verify = False
self.recipient = recipient
def create_catalog(self, data={}):
response = self.session.post(self.recipient + "/api/catalogs", json=data)
return response.headers["Location"]
def create_offered_resource(self, data={}):
response = self.session.post(self.recipient + "/api/offers", json=data)
return response.headers["Location"]
def create_representation(self, data={}):
response = self.session.post(self.recipient + "/api/representations", json=data)
return response.headers["Location"]
def create_artifact(self, data={"value": "SOME LONG VALUE"}):
response = self.session.post(self.recipient + "/api/artifacts", json=data)
return response.headers["Location"]
def update_artifact(self, artifact, data) -> bool:
response = self.session.put(artifact, json=data)
return response.status_code == 204
def create_contract(
self,
data={
"start": "2021-04-06T13:33:44.995+02:00",
"end": "2021-12-06T13:33:44.995+02:00",
},
):
response = self.session.post(self.recipient + "/api/contracts", json=data)
return response.headers["Location"]
def create_rule(
self,
data={
"value": """{
"@context" : {
"ids" : "https://w3id.org/idsa/core/",
"idsc" : "https://w3id.org/idsa/code/"
},
"@type": "ids:Permission",
"@id": "https://w3id.org/idsa/autogen/permission/cf1cb758-b96d-4486-b0a7-f3ac0e289588",
"ids:action": [
{
"@id": "idsc:USE"
}
],
"ids:description": [
{
"@value": "provide-access",
"@type": "http://www.w3.org/2001/XMLSchema#string"
}
],
"ids:title": [
{
"@value": "Example Usage Policy",
"@type": "http://www.w3.org/2001/XMLSchema#string"
}
]
}"""
},
):
response = self.session.post(self.recipient + "/api/rules", json=data)
return response.headers["Location"]
def add_resource_to_catalog(self, catalog, resource):
return self.session.post(
catalog + "/offers", json=self.toListIfNeeded(resource)
)
def add_catalog_to_resource(self, resource, catalog):
return self.session.post(
resource + "/catalogs", json=self.toListIfNeeded(catalog)
)
def add_representation_to_resource(self, resource, representation):
return self.session.post(
resource + "/representations", json=self.toListIfNeeded(representation)
)
def add_artifact_to_representation(self, representation, artifact):
return self.session.post(
representation + "/artifacts", json=self.toListIfNeeded(artifact)
)
def add_contract_to_resource(self, resource, contract):
return self.session.post(
resource + "/contracts", json=self.toListIfNeeded(contract)
)
def add_rule_to_contract(self, contract, rule):
return self.session.post(contract + "/rules", json=self.toListIfNeeded(rule))
def toListIfNeeded(self, obj):
if isinstance(obj, list):
return obj
else:
return [obj]
def get_data(self, artifact):
return self.session.get(artifact + "/data")
def get_artifacts_for_agreement(self, agreement):
return json.loads(self.session.get(agreement + "/artifacts").text)
#!/usr/bin/env python3
#
# Copyright 2020 Fraunhofer Institute for Software and Systems Engineering
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import requests
# Suppress ssl verification warning
requests.packages.urllib3.disable_warnings()
class SubscriptionApi:
session = None
recipient = None
def __init__(self, recipient, auth=("admin", "password")):
self.session = requests.Session()
self.session.auth = auth
self.session.verify = False
self.recipient = recipient
def create_subscription(self, data={}):
response = self.session.post(self.recipient + "/api/subscriptions", json=data)
return response.headers["Location"]
def subscription_message(self, data={}, params={}):
response = self.session.post(
self.recipient + "/api/ids/subscribe", json=data, params=params
)
return response
def get_subscriptions(self):
response = self.session.get(self.recipient + "/api/subscriptions")
return response
from src.connectorAPI.idsapi import IdsApi
from src.connectorAPI.resourceapi import ResourceApi
def get_text(conf):
consumer_url = conf.custom_dsc if conf.use_custom_dsc else "https://localhost:8080"
consumer = IdsApi(consumer_url)
response = consumer.contractRequest(
conf.provider_url_downloading, conf.resource_id, conf.artifact_id, False, conf.contract
)
agreement = response["_links"]["self"]["href"]
consumer_resources = ResourceApi(consumer_url)
artifacts = consumer_resources.get_artifacts_for_agreement(agreement)
first_artifact = artifacts["_embedded"]["artifacts"][0]["_links"]["self"]["href"]
data = consumer_resources.get_data(first_artifact).text
print(data)
return data
import os.path
import pprint
from flask import Flask, render_template, request
from flask_bootstrap import Bootstrap
from flask_marshmallow import Marshmallow
from flask_wtf import FlaskForm
from wtforms import StringField, BooleanField, SubmitField
from wtforms.validators import DataRequired
from src.dsc_grpc_service import get_text
from src.state.configuration_state import Configuration, get_jsonifyed_configuration
template_dir = os.path.abspath('templates')
app = Flask(__name__, template_folder=template_dir)
ma = Marshmallow(app)
conf = None
class HppInputForm(FlaskForm):
recipient_str = StringField('Recipient', validators=[DataRequired(), ])
resource_id_str = StringField('Resource Id', validators=[DataRequired(), ])
artifact_id_str = StringField('Artifact Id', validators=[DataRequired(), ])
contract_input_str = StringField('Contract', validators=[DataRequired(), ])
custom_consumer_toggle = BooleanField('Use Custom Consumer')
custom_consumer_str = StringField('Custom Consumer')
submit = SubmitField('Submit Configuration')
class PullForm(FlaskForm):
submit = SubmitField('Pull Data')
@app.route('/api/v1/recipient', methods=["POST"])
def set_recipient():
conf.recipient = request.get_json()['recipient']
pprint.pprint(conf.recipient)
return get_jsonifyed_configuration()
@app.route('/api/v1/resourceId', methods=["POST"])
def set_resource_id():
conf.resource_id = request.get_json()['resourceId']
return get_jsonifyed_configuration()
@app.route('/api/v1/artifactId', methods=["POST"])
def set_artifact_id():
conf.artifact_id = request.get_json()['artifactId']
return get_jsonifyed_configuration()
@app.route('/api/v1/download', methods=["POST"])
def set_download():
conf.download = request.get_json()['download']
return get_jsonifyed_configuration()
@app.route('/api/v1/contract', methods=["POST"])
def set_contract():
conf.contract = request.get_json()['contract']
return get_jsonifyed_configuration()
@app.route('/api/v1/useCustomDSC', methods=["POST"])
def set_use_custom_dsc():
conf.use_custom_dsc = request.get_json()['useCustomDSC']
return get_jsonifyed_configuration()
@app.route('/api/v1/data', methods=["GET"])
def get_data():
data = get_text(conf)
return data
@app.route('/api/v1/customDSC', methods=["POST"])
def set_custom_dsc():
conf.custom_dsc = request.get_json()['customDSC']
return get_jsonifyed_configuration()
@app.route('/', methods=["GET", "POST"])
@app.route('/hpp_input', methods=["GET", "POST"])
def index():
form = HppInputForm()
pull_form = PullForm()
if pull_form.submit.data and pull_form.validate_on_submit():
return render_template("index.html", form=form, data=get_text(conf), current_configuration=Configuration(),
pull_form=pull_form)
if form.submit.data and form.validate_on_submit():
print('processing User Input')
conf.recipient = form.recipient_str.data
conf.resource_id = form.resource_id_str.data
conf.artifact_id = form.artifact_id_str.data
conf.contract = form.contract_input_str.data
conf.use_custom_dsc = form.custom_consumer_toggle.data
conf.custom_dsc = form.custom_consumer_str.data
return render_template("index.html", form=form, current_configuration=Configuration(), pull_form=pull_form)
return render_template('index.html', form=form, current_configuration=Configuration(), pull_form=pull_form)
def run_flask_app(host: str, port: int, p_conf):
global conf
conf = p_conf
app.secret_key = "dscmodel"
bootstrap = Bootstrap(app)
app.app_context().push()
app.run(host=host, port=port)
import grpc
from concurrent import futures
import model_pb2
import model_pb2_grpc
import src.dsc_grpc_service as dgs
from src.state.configuration_state import Configuration
class TextServicer(model_pb2_grpc.IDSTextConnectorServicer):
conf:Configuration = None
def get_text(self, request, context):
response = model_pb2.Text()
if self.conf.data_send:
self.conf.data_send = False
context.set_code(grpc.StatusCode.NOT_FOUND)
context.set_details('All available data has been processed')
else:
self.conf.data_send = True
response.text = dgs.get_text(self.conf)
return response
def start_server(port: int, conf):
print("starting grpc server")
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
servicer = TextServicer()
servicer.conf = conf
model_pb2_grpc.add_IDSTextConnectorServicer_to_server(servicer, server)
print("Starting model_grpc Server. Listening on port : " + str(port))
server.add_insecure_port("[::]:{}".format(port))
server.start()
return server
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment