mirror of
https://github.com/gsi-upm/senpy
synced 2025-10-19 17:58:28 +00:00
Compare commits
40 Commits
0.5.7
...
0.7.1-5-g8
Author | SHA1 | Date | |
---|---|---|---|
|
87589e994a | ||
|
9f6a6f5ecd | ||
|
3cea7534ef | ||
|
7eaf303124 | ||
|
b4ca5f4a7c | ||
|
3311af2167 | ||
|
a4694dff2c | ||
|
6cb669cdb1 | ||
|
506feec13d | ||
|
2e3a6b7c84 | ||
|
7cc8b562f4 | ||
|
528bbcac35 | ||
|
068241fb72 | ||
|
39d86a2050 | ||
|
5371c83ab0 | ||
|
673992dbe8 | ||
|
eb3a42c247 | ||
|
20357d2a0d | ||
|
e9d7980e42 | ||
|
908090f634 | ||
|
cb963dc438 | ||
|
477cb18db1 | ||
|
fbf0384985 | ||
|
7a2c016cc6 | ||
|
b072121e20 | ||
|
ceed9b97d0 | ||
|
2dbdb58b06 | ||
|
db30257373 | ||
|
7fd69cc690 | ||
|
b543a4614e | ||
|
bc1f9e4cf5 | ||
|
d72a995fa9 | ||
|
40b67503ce | ||
|
8624562f02 | ||
|
4dee623ef9 | ||
|
2e7530d9bc | ||
|
07b5dd3823 | ||
|
0d511ad3c3 | ||
|
7205a0e7b2 | ||
|
fff38bf825 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,4 +5,4 @@ dist
|
|||||||
build
|
build
|
||||||
README.html
|
README.html
|
||||||
__pycache__
|
__pycache__
|
||||||
Dockerfile-*
|
VERSION
|
||||||
|
80
.gitlab-ci.yml
Normal file
80
.gitlab-ci.yml
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
image: docker:latest
|
||||||
|
|
||||||
|
|
||||||
|
# When using dind, it's wise to use the overlayfs driver for
|
||||||
|
# improved performance.
|
||||||
|
variables:
|
||||||
|
DOCKER_DRIVER: overlay
|
||||||
|
DOCKERFILE: Dockerfile
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
- sh version.sh > senpy/VERSION
|
||||||
|
|
||||||
|
stages:
|
||||||
|
- test
|
||||||
|
- images
|
||||||
|
- release
|
||||||
|
|
||||||
|
.test: &test_definition
|
||||||
|
variables:
|
||||||
|
PIP_CACHE_DIR: "$CI_PROJECT_DIR/pip-cache"
|
||||||
|
cache:
|
||||||
|
paths:
|
||||||
|
- .eggs/
|
||||||
|
- "$CI_PROJECT_DIR/pip-cache"
|
||||||
|
- .venv
|
||||||
|
key: "$CI_PROJECT_NAME"
|
||||||
|
stage: test
|
||||||
|
script:
|
||||||
|
- pip install --use-wheel -U pip setuptools virtualenv
|
||||||
|
- virtualenv .venv/$PYTHON_VERSION
|
||||||
|
- source .venv/$PYTHON_VERSION/bin/activate
|
||||||
|
- pip install --use-wheel -r requirements.txt
|
||||||
|
- pip install --use-wheel -r test-requirements.txt
|
||||||
|
- py.test --cov=senpy --cov-report term-missing
|
||||||
|
- python
|
||||||
|
|
||||||
|
test-3.5:
|
||||||
|
<<: *test_definition
|
||||||
|
image: "python:3.5"
|
||||||
|
|
||||||
|
test-2.7:
|
||||||
|
<<: *test_definition
|
||||||
|
image: "python:2.7"
|
||||||
|
|
||||||
|
|
||||||
|
.image: &image_definition
|
||||||
|
variables:
|
||||||
|
PYTHON_VERSION: "3.5"
|
||||||
|
before_script:
|
||||||
|
- docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN $CI_REGISTRY
|
||||||
|
script:
|
||||||
|
- docker build -f Dockerfile-3.5 . -t $CI_REGISTRY_IMAGE:$CI_BUILD_REF_SLUG-$PYTHON_VERSION
|
||||||
|
- docker push $CI_REGISTRY_IMAGE:$CI_BUILD_REF_SLUG-$PYTHON_VERSION
|
||||||
|
stage: images
|
||||||
|
only:
|
||||||
|
- tags
|
||||||
|
- triggers
|
||||||
|
|
||||||
|
image-3.5:
|
||||||
|
<<: *image_definition
|
||||||
|
variables:
|
||||||
|
PYTHON_VERSION: "3.5"
|
||||||
|
|
||||||
|
image-2.7:
|
||||||
|
<<: *image_definition
|
||||||
|
variables:
|
||||||
|
PYTHON_VERSION: "2.7"
|
||||||
|
|
||||||
|
image-latest:
|
||||||
|
stage: release
|
||||||
|
before_script:
|
||||||
|
- docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN $CI_REGISTRY
|
||||||
|
script:
|
||||||
|
- docker build -f Dockerfile . -t $CI_REGISTRY_IMAGE
|
||||||
|
- docker tag $CI_REGISTRY_IMAGE $CI_REGISTRY_IMAGE:$CI_BUILD_REF_SLUG
|
||||||
|
- docker push $CI_REGISTRY_IMAGE
|
||||||
|
- docker push $CI_REGISTRY_IMAGE:$CI_BUILD_REF_SLUG
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
- triggers
|
@@ -1,6 +1,8 @@
|
|||||||
language: python
|
language: python
|
||||||
python:
|
python:
|
||||||
- "2.7"
|
- "2.7"
|
||||||
|
- "3.4"
|
||||||
|
- "3.5"
|
||||||
install: "pip install -r requirements.txt"
|
install: "pip install -r requirements.txt"
|
||||||
# run nosetests - Tests
|
# run nosetests - Tests
|
||||||
script: nosetests
|
script: nosetests
|
||||||
|
1
Dockerfile
Symbolic link
1
Dockerfile
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
Dockerfile-3.5
|
21
Dockerfile-2.7
Normal file
21
Dockerfile-2.7
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
from python:2.7
|
||||||
|
|
||||||
|
RUN mkdir /cache/
|
||||||
|
ENV PIP_CACHE_DIR=/cache/
|
||||||
|
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
ADD requirements.txt /usr/src/app/
|
||||||
|
RUN pip install --use-wheel -r requirements.txt
|
||||||
|
ADD . /usr/src/app/
|
||||||
|
RUN pip install .
|
||||||
|
|
||||||
|
|
||||||
|
VOLUME /data/
|
||||||
|
|
||||||
|
RUN mkdir /senpy-plugins/
|
||||||
|
|
||||||
|
WORKDIR /senpy-plugins/
|
||||||
|
ONBUILD ADD . /senpy-plugins/
|
||||||
|
ONBUILD RUN python -m senpy --only-install -f /senpy-plugins
|
||||||
|
|
||||||
|
ENTRYPOINT ["python", "-m", "senpy", "-f", "/senpy-plugins/", "--host", "0.0.0.0"]
|
21
Dockerfile-3.4
Normal file
21
Dockerfile-3.4
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
from python:3.4
|
||||||
|
|
||||||
|
RUN mkdir /cache/
|
||||||
|
ENV PIP_CACHE_DIR=/cache/
|
||||||
|
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
ADD requirements.txt /usr/src/app/
|
||||||
|
RUN pip install --use-wheel -r requirements.txt
|
||||||
|
ADD . /usr/src/app/
|
||||||
|
RUN pip install .
|
||||||
|
|
||||||
|
|
||||||
|
VOLUME /data/
|
||||||
|
|
||||||
|
RUN mkdir /senpy-plugins/
|
||||||
|
|
||||||
|
WORKDIR /senpy-plugins/
|
||||||
|
ONBUILD ADD . /senpy-plugins/
|
||||||
|
ONBUILD RUN python -m senpy --only-install -f /senpy-plugins
|
||||||
|
|
||||||
|
ENTRYPOINT ["python", "-m", "senpy", "-f", "/senpy-plugins/", "--host", "0.0.0.0"]
|
21
Dockerfile-3.5
Normal file
21
Dockerfile-3.5
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
FROM python:3.5
|
||||||
|
|
||||||
|
RUN mkdir /cache/
|
||||||
|
ENV PIP_CACHE_DIR=/cache/
|
||||||
|
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
ADD requirements.txt /usr/src/app/
|
||||||
|
RUN pip install --use-wheel -r requirements.txt
|
||||||
|
ADD . /usr/src/app/
|
||||||
|
RUN pip install .
|
||||||
|
|
||||||
|
|
||||||
|
VOLUME /data/
|
||||||
|
|
||||||
|
RUN mkdir /senpy-plugins/
|
||||||
|
|
||||||
|
WORKDIR /senpy-plugins/
|
||||||
|
ONBUILD ADD . /senpy-plugins/
|
||||||
|
ONBUILD RUN python -m senpy --only-install -f /senpy-plugins
|
||||||
|
|
||||||
|
ENTRYPOINT ["python", "-m", "senpy", "-f", "/senpy-plugins/", "--host", "0.0.0.0"]
|
@@ -1,3 +1,21 @@
|
|||||||
from python:{{PYVERSION}}-onbuild
|
from python:{{PYVERSION}}
|
||||||
|
|
||||||
ENTRYPOINT ["python", "-m", "senpy", "-f", ".", "--host", "0.0.0.0"]
|
RUN mkdir /cache/
|
||||||
|
ENV PIP_CACHE_DIR=/cache/
|
||||||
|
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
ADD requirements.txt /usr/src/app/
|
||||||
|
RUN pip install --use-wheel -r requirements.txt
|
||||||
|
ADD . /usr/src/app/
|
||||||
|
RUN pip install .
|
||||||
|
|
||||||
|
|
||||||
|
VOLUME /data/
|
||||||
|
|
||||||
|
RUN mkdir /senpy-plugins/
|
||||||
|
|
||||||
|
WORKDIR /senpy-plugins/
|
||||||
|
ONBUILD ADD . /senpy-plugins/
|
||||||
|
ONBUILD RUN python -m senpy --only-install -f /senpy-plugins
|
||||||
|
|
||||||
|
ENTRYPOINT ["python", "-m", "senpy", "-f", "/senpy-plugins/", "--host", "0.0.0.0"]
|
@@ -1,7 +1,6 @@
|
|||||||
include requirements.txt
|
include requirements.txt
|
||||||
include test-requirements.txt
|
include test-requirements.txt
|
||||||
include README.md
|
include README.rst
|
||||||
include senpy/context.jsonld
|
|
||||||
include senpy/VERSION
|
include senpy/VERSION
|
||||||
graft senpy/plugins
|
graft senpy/plugins
|
||||||
graft senpy/schemas
|
graft senpy/schemas
|
||||||
|
93
Makefile
93
Makefile
@@ -1,29 +1,102 @@
|
|||||||
PYVERSIONS=2.7 3.4
|
PYVERSIONS=3.5 3.4 2.7
|
||||||
|
PYMAIN=$(firstword $(PYVERSIONS))
|
||||||
NAME=senpy
|
NAME=senpy
|
||||||
REPO=gsiupm
|
REPO=gsiupm
|
||||||
VERSION=$(shell cat $(NAME)/VERSION)
|
VERSION=$(shell ./version.sh)
|
||||||
|
TARNAME=$(NAME)-$(VERSION).tar.gz
|
||||||
|
IMAGENAME=$(REPO)/$(NAME):$(VERSION)
|
||||||
|
TEST_COMMAND=gitlab-runner exec docker --cache-dir=/tmp/gitlabrunner --docker-volumes /tmp/gitlabrunner:/tmp/gitlabrunner --env CI_PROJECT_NAME=$(NAME)
|
||||||
|
|
||||||
all: build run
|
all: build run
|
||||||
|
|
||||||
|
FORCE:
|
||||||
|
|
||||||
|
version: FORCE
|
||||||
|
@echo $(VERSION) > $(NAME)/VERSION
|
||||||
|
@echo $(VERSION)
|
||||||
|
|
||||||
|
yapf:
|
||||||
|
yapf -i -r senpy
|
||||||
|
yapf -i -r tests
|
||||||
|
|
||||||
|
dev:
|
||||||
|
pip install --user pre-commit
|
||||||
|
pre-commit install
|
||||||
|
|
||||||
dockerfiles: $(addprefix Dockerfile-,$(PYVERSIONS))
|
dockerfiles: $(addprefix Dockerfile-,$(PYVERSIONS))
|
||||||
|
@unlink Dockerfile >/dev/null
|
||||||
|
ln -s Dockerfile-$(PYMAIN) Dockerfile
|
||||||
|
|
||||||
Dockerfile-%: Dockerfile.template
|
Dockerfile-%: Dockerfile.template
|
||||||
sed "s/{{PYVERSION}}/$*/" Dockerfile.template > Dockerfile-$*
|
sed "s/{{PYVERSION}}/$*/" Dockerfile.template > Dockerfile-$*
|
||||||
|
|
||||||
|
quick_build: $(addprefix build-, $(PYMAIN))
|
||||||
|
|
||||||
build: $(addprefix build-, $(PYVERSIONS))
|
build: $(addprefix build-, $(PYVERSIONS))
|
||||||
|
|
||||||
build-%: Dockerfile-%
|
build-%: Dockerfile-%
|
||||||
docker build -t '$(REPO)/$(NAME):$(VERSION)-python$*' -f Dockerfile-$* .;
|
docker build -t '$(IMAGENAME)-python$*' -f Dockerfile-$* .;
|
||||||
|
|
||||||
|
quick_test: $(addprefix test-,$(PYMAIN))
|
||||||
|
|
||||||
test: $(addprefix test-,$(PYVERSIONS))
|
test: $(addprefix test-,$(PYVERSIONS))
|
||||||
|
|
||||||
test-%: build-%
|
debug-%:
|
||||||
docker run --rm -w /usr/src/app/ --entrypoint=/usr/local/bin/python -ti '$(REPO)/$(NAME):$(VERSION)-python$*' setup.py test ;
|
@docker start $(NAME)-debug || (\
|
||||||
|
$(MAKE) build-$*; \
|
||||||
|
docker run -d -w /usr/src/app/ -v $$PWD:/usr/src/app --entrypoint=/bin/bash -p 5000:5000 -ti --name $(NAME)-debug '$(IMAGENAME)-python$*'; \
|
||||||
|
docker exec -ti $(NAME)-debug pip install -r test-requirements.txt; \
|
||||||
|
)\
|
||||||
|
|
||||||
test_pip-%:
|
docker attach $(NAME)-debug
|
||||||
docker run --rm -ti python:$* pip -q install senpy ;
|
|
||||||
|
|
||||||
test_pip: $(addprefix test_pip-,$(PYVERSIONS))
|
debug: debug-$(PYMAIN)
|
||||||
|
|
||||||
.PHONY: test test-% build-% build test test_pip
|
test-%:
|
||||||
|
$(TEST_COMMAND) test-$*
|
||||||
|
|
||||||
|
dist/$(TARNAME):
|
||||||
|
docker run --rm -ti -v $$PWD:/usr/src/app/ -w /usr/src/app/ python:$(PYMAIN) python setup.py sdist;
|
||||||
|
|
||||||
|
sdist: dist/$(TARNAME)
|
||||||
|
|
||||||
|
pip_test-%: sdist
|
||||||
|
docker run --rm -v $$PWD/dist:/dist/ -ti python:$* pip install /dist/$(TARNAME);
|
||||||
|
|
||||||
|
pip_test: $(addprefix pip_test-,$(PYVERSIONS))
|
||||||
|
|
||||||
|
upload-%: test-%
|
||||||
|
docker push '$(IMAGENAME)-python$*'
|
||||||
|
|
||||||
|
upload: test $(addprefix upload-,$(PYVERSIONS))
|
||||||
|
docker tag '$(IMAGENAME)-python$(PYMAIN)' '$(IMAGENAME)'
|
||||||
|
docker tag '$(IMAGENAME)-python$(PYMAIN)' '$(REPO)/$(NAME)'
|
||||||
|
docker push '$(IMAGENAME)'
|
||||||
|
docker push '$(REPO)/$(NAME)'
|
||||||
|
|
||||||
|
clean:
|
||||||
|
@docker ps -a | awk '/$(REPO)\/$(NAME)/{ split($$2, vers, "-"); if(vers[0] != "${VERSION}"){ print $$1;}}' | xargs docker rm -v 2>/dev/null|| true
|
||||||
|
@docker images | awk '/$(REPO)\/$(NAME)/{ split($$2, vers, "-"); if(vers[0] != "${VERSION}"){ print $$1":"$$2;}}' | xargs docker rmi 2>/dev/null|| true
|
||||||
|
@docker rmi $(NAME)-debug 2>/dev/null || true
|
||||||
|
|
||||||
|
|
||||||
|
git_commit:
|
||||||
|
git commit -a
|
||||||
|
|
||||||
|
git_tag:
|
||||||
|
git tag ${VERSION}
|
||||||
|
|
||||||
|
upload_git:
|
||||||
|
git push --tags origin master
|
||||||
|
|
||||||
|
pip_upload:
|
||||||
|
python setup.py sdist upload ;
|
||||||
|
|
||||||
|
pip_test: $(addprefix pip_test-,$(PYVERSIONS))
|
||||||
|
|
||||||
|
run-%: build-%
|
||||||
|
docker run --rm -p 5000:5000 -ti '$(IMAGENAME)-python$(PYMAIN)' --default-plugins
|
||||||
|
|
||||||
|
run: run-$(PYMAIN)
|
||||||
|
|
||||||
|
.PHONY: test test-% build-% build test pip_test run yapf dev
|
||||||
|
@@ -12,7 +12,7 @@ Have you ever wanted to turn your sentiment analysis algorithms into a service?
|
|||||||
With senpy, now you can.
|
With senpy, now you can.
|
||||||
It provides all the tools so you just have to worry about improving your algorithms:
|
It provides all the tools so you just have to worry about improving your algorithms:
|
||||||
|
|
||||||
`See it in action. <http://demos.gsi.dit.upm.es/senpy>`_
|
`See it in action. <http://senpy.cluster.gsi.dit.upm.es/>`_
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
------------
|
------------
|
||||||
@@ -38,9 +38,9 @@ If you want to install senpy globally, use sudo instead of the ``--user`` flag.
|
|||||||
|
|
||||||
Docker Image
|
Docker Image
|
||||||
************
|
************
|
||||||
Build the image or use the pre-built one: ``docker run -ti -p 5000:5000 balkian/senpy --host 0.0.0.0 --default-plugins``.
|
Build the image or use the pre-built one: ``docker run -ti -p 5000:5000 gsiupm/senpy --host 0.0.0.0 --default-plugins``.
|
||||||
|
|
||||||
To add custom plugins, add a volume and tell senpy where to find the plugins: ``docker run -ti -p 5000:5000 -v <PATH OF PLUGINS>:/plugins balkian/senpy --host 0.0.0.0 --default-plugins -f /plugins``
|
To add custom plugins, add a volume and tell senpy where to find the plugins: ``docker run -ti -p 5000:5000 -v <PATH OF PLUGINS>:/plugins gsiupm/senpy --host 0.0.0.0 --default-plugins -f /plugins``
|
||||||
|
|
||||||
Usage
|
Usage
|
||||||
-----
|
-----
|
||||||
|
43
app.py
43
app.py
@@ -1,43 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2014 J. Fernando Sánchez Rada - Grupo de Sistemas Inteligentes
|
|
||||||
# DIT, UPM
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
"""
|
|
||||||
This is a helper for development. If you want to run Senpy use:
|
|
||||||
|
|
||||||
python -m senpy
|
|
||||||
"""
|
|
||||||
from gevent.monkey import patch_all; patch_all()
|
|
||||||
import gevent
|
|
||||||
import config
|
|
||||||
from flask import Flask
|
|
||||||
from senpy.extensions import Senpy
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
from gevent.wsgi import WSGIServer
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
|
||||||
mypath = os.path.dirname(os.path.realpath(__file__))
|
|
||||||
sp = Senpy(app, os.path.join(mypath, "plugins"), default_plugins=True)
|
|
||||||
sp.activate_all()
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
import logging
|
|
||||||
logging.basicConfig(level=config.DEBUG)
|
|
||||||
app.debug = config.DEBUG
|
|
||||||
http_server = WSGIServer(('', config.SERVER_PORT), app)
|
|
||||||
http_server.serve_forever()
|
|
1
docs/_static/schemas
vendored
Symbolic link
1
docs/_static/schemas
vendored
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../../senpy/schemas/
|
4
docs/bad-examples/plugins/noplugins.json
Normal file
4
docs/bad-examples/plugins/noplugins.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"plugins": [
|
||||||
|
]
|
||||||
|
}
|
18
docs/bad-examples/results/example-basic-FAIL.json
Normal file
18
docs/bad-examples/results/example-basic-FAIL.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"@context": "http://mixedemotions-project.eu/ns/context.jsonld",
|
||||||
|
"@id": "http://example.com#NIFExample",
|
||||||
|
"@type": "results",
|
||||||
|
"analysis": [
|
||||||
|
],
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"@type": [
|
||||||
|
"nif:RFC5147String",
|
||||||
|
"nif:Context"
|
||||||
|
],
|
||||||
|
"nif:beginIndex": 0,
|
||||||
|
"nif:endIndex": 40,
|
||||||
|
"nif:isString": "My favourite actress is Natalie Portman"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@@ -1,4 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
# flake8: noqa
|
||||||
#
|
#
|
||||||
# Senpy documentation build configuration file, created by
|
# Senpy documentation build configuration file, created by
|
||||||
# sphinx-quickstart on Tue Feb 24 08:57:32 2015.
|
# sphinx-quickstart on Tue Feb 24 08:57:32 2015.
|
||||||
@@ -52,16 +53,17 @@ master_doc = 'index'
|
|||||||
|
|
||||||
# General information about the project.
|
# General information about the project.
|
||||||
project = u'Senpy'
|
project = u'Senpy'
|
||||||
copyright = u'2015, J. Fernando Sánchez'
|
copyright = u'2016, J. Fernando Sánchez'
|
||||||
|
|
||||||
# The version info for the project you're documenting, acts as replacement for
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
# |version| and |release|, also used in various other places throughout the
|
# |version| and |release|, also used in various other places throughout the
|
||||||
# built documents.
|
# built documents.
|
||||||
#
|
#
|
||||||
# The short X.Y version.
|
# The short X.Y version.
|
||||||
version = '0.4'
|
with open('../senpy/VERSION') as f:
|
||||||
|
version = f.read().strip()
|
||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
release = '0.4'
|
release = version
|
||||||
|
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
# for a list of supported languages.
|
# for a list of supported languages.
|
||||||
|
5
docs/examples/plugins/noplugins.json
Normal file
5
docs/examples/plugins/noplugins.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"@type": "plugins",
|
||||||
|
"plugins": [
|
||||||
|
]
|
||||||
|
}
|
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"@context": "http://mixedemotions-project.eu/ns/context.jsonld",
|
"@context": "http://mixedemotions-project.eu/ns/context.jsonld",
|
||||||
"@id": "http://example.com#NIFExample",
|
"@id": "http://example.com#NIFExample",
|
||||||
|
"@type": "results",
|
||||||
"analysis": [
|
"analysis": [
|
||||||
],
|
],
|
||||||
"entries": [
|
"entries": [
|
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"@context": "http://mixedemotions-project.eu/ns/context.jsonld",
|
"@context": "http://mixedemotions-project.eu/ns/context.jsonld",
|
||||||
"@id": "me:Result1",
|
"@id": "me:Result1",
|
||||||
|
"@type": "results",
|
||||||
"analysis": [
|
"analysis": [
|
||||||
{
|
{
|
||||||
"@id": "me:SAnalysis1",
|
"@id": "me:SAnalysis1",
|
||||||
@@ -52,7 +53,8 @@
|
|||||||
"@id": "http://micro.blog/status1#char=16,77",
|
"@id": "http://micro.blog/status1#char=16,77",
|
||||||
"nif:beginIndex": 16,
|
"nif:beginIndex": 16,
|
||||||
"nif:endIndex": 77,
|
"nif:endIndex": 77,
|
||||||
"nif:anchorOf": "put your Windows Phone on your newest #open technology program"
|
"nif:anchorOf": "put your Windows Phone on your newest #open technology program",
|
||||||
|
"prov:wasGeneratedBy": "me:SgAnalysis1"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"sentiments": [
|
"sentiments": [
|
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"@context": "http://mixedemotions-project.eu/ns/context.jsonld",
|
"@context": "http://mixedemotions-project.eu/ns/context.jsonld",
|
||||||
"@id": "me:Result1",
|
"@id": "me:Result1",
|
||||||
|
"@type": "results",
|
||||||
"analysis": [
|
"analysis": [
|
||||||
{
|
{
|
||||||
"@id": "me:EmotionAnalysis1",
|
"@id": "me:EmotionAnalysis1",
|
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"@context": "http://mixedemotions-project.eu/ns/context.jsonld",
|
"@context": "http://mixedemotions-project.eu/ns/context.jsonld",
|
||||||
"@id": "me:Result1",
|
"@id": "me:Result1",
|
||||||
|
"@type": "results",
|
||||||
"analysis": [
|
"analysis": [
|
||||||
{
|
{
|
||||||
"@id": "me:NER1",
|
"@id": "me:NER1",
|
46
docs/examples/results/example-pad.json
Normal file
46
docs/examples/results/example-pad.json
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"@context": [
|
||||||
|
"http://mixedemotions-project.eu/ns/context.jsonld",
|
||||||
|
{
|
||||||
|
"emovoc": "http://www.gsi.dit.upm.es/ontologies/onyx/vocabularies/emotionml/ns#"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@id": "me:Result1",
|
||||||
|
"@type": "results",
|
||||||
|
"analysis": [
|
||||||
|
{
|
||||||
|
"@id": "me:HesamsAnalysis",
|
||||||
|
"@type": "onyx:EmotionAnalysis",
|
||||||
|
"onyx:usesEmotionModel": "emovoc:pad-dimensions"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"@id": "Entry1",
|
||||||
|
"@type": [
|
||||||
|
"nif:RFC5147String",
|
||||||
|
"nif:Context"
|
||||||
|
],
|
||||||
|
"nif:isString": "This is a test string",
|
||||||
|
"entities": [
|
||||||
|
],
|
||||||
|
"suggestions": [
|
||||||
|
],
|
||||||
|
"sentiments": [
|
||||||
|
],
|
||||||
|
"emotions": [
|
||||||
|
{
|
||||||
|
"@id": "Entry1#char=0,21",
|
||||||
|
"nif:anchorOf": "This is a test string",
|
||||||
|
"prov:wasGeneratedBy": "me:HesamAnalysis",
|
||||||
|
"onyx:hasEmotion": [
|
||||||
|
{
|
||||||
|
"emovoc:pleasure": 0.5,
|
||||||
|
"emovoc:arousal": 0.7
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"@context": "http://mixedemotions-project.eu/ns/context.jsonld",
|
"@context": "http://mixedemotions-project.eu/ns/context.jsonld",
|
||||||
"@id": "me:Result1",
|
"@id": "me:Result1",
|
||||||
|
"@type": "results",
|
||||||
"analysis": [
|
"analysis": [
|
||||||
{
|
{
|
||||||
"@id": "me:SAnalysis1",
|
"@id": "me:SAnalysis1",
|
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"@context": "http://mixedemotions-project.eu/ns/context.jsonld",
|
"@context": "http://mixedemotions-project.eu/ns/context.jsonld",
|
||||||
"@id": "me:Result1",
|
"@id": "me:Result1",
|
||||||
|
"@type": "results",
|
||||||
"analysis": [
|
"analysis": [
|
||||||
{
|
{
|
||||||
"@id": "me:SgAnalysis1",
|
"@id": "me:SgAnalysis1",
|
||||||
@@ -23,7 +24,8 @@
|
|||||||
"@id": "http://micro.blog/status1#char=16,77",
|
"@id": "http://micro.blog/status1#char=16,77",
|
||||||
"nif:beginIndex": 16,
|
"nif:beginIndex": 16,
|
||||||
"nif:endIndex": 77,
|
"nif:endIndex": 77,
|
||||||
"nif:anchorOf": "put your Windows Phone on your newest #open technology program"
|
"nif:anchorOf": "put your Windows Phone on your newest #open technology program",
|
||||||
|
"prov:wasGeneratedBy": "me:SgAnalysis1"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"sentiments": [
|
"sentiments": [
|
@@ -22,6 +22,6 @@ If you want to install senpy globally, use sudo instead of the ``--user`` flag.
|
|||||||
|
|
||||||
Docker Image
|
Docker Image
|
||||||
************
|
************
|
||||||
Build the image or use the pre-built one: ``docker run -ti -p 5000:5000 balkian/senpy --host 0.0.0.0 --default-plugins``.
|
Build the image or use the pre-built one: ``docker run -ti -p 5000:5000 gsiupm/senpy --host 0.0.0.0 --default-plugins``.
|
||||||
|
|
||||||
To add custom plugins, add a volume and tell senpy where to find the plugins: ``docker run -ti -p 5000:5000 -v <PATH OF PLUGINS>:/plugins balkian/senpy --host 0.0.0.0 --default-plugins -f /plugins``
|
To add custom plugins, add a volume and tell senpy where to find the plugins: ``docker run -ti -p 5000:5000 -v <PATH OF PLUGINS>:/plugins gsiupm/senpy --host 0.0.0.0 --default-plugins -f /plugins``
|
||||||
|
138
docs/plugins.rst
138
docs/plugins.rst
@@ -2,46 +2,127 @@ Developing new plugins
|
|||||||
----------------------
|
----------------------
|
||||||
Each plugin represents a different analysis process.There are two types of files that are needed by senpy for loading a plugin:
|
Each plugin represents a different analysis process.There are two types of files that are needed by senpy for loading a plugin:
|
||||||
|
|
||||||
Plugins Interface
|
|
||||||
=======
|
|
||||||
- Definition file, has the ".senpy" extension.
|
- Definition file, has the ".senpy" extension.
|
||||||
- Code file, is a python file.
|
- Code file, is a python file.
|
||||||
|
|
||||||
|
This separation will allow us to deploy plugins that use the same code but employ different parameters.
|
||||||
|
For instance, one could use the same classifier and processing in several plugins, but train with different datasets.
|
||||||
|
This scenario is particularly useful for evaluation purposes.
|
||||||
|
|
||||||
|
The only limitation is that the name of each plugin needs to be unique.
|
||||||
|
|
||||||
Plugins Definitions
|
Plugins Definitions
|
||||||
===================
|
===================
|
||||||
|
|
||||||
The definition file can be written in JSON or YAML, where the data representation consists on attribute-value pairs.
|
The definition file contains all the attributes of the plugin, and can be written in YAML or JSON.
|
||||||
The principal attributes are:
|
The most important attributes are:
|
||||||
|
|
||||||
* name: plugin name used in senpy to call the plugin.
|
* **name**: unique name that senpy will use internally to identify the plugin.
|
||||||
* module: indicates the module that will be loaded
|
* **module**: indicates the module that contains the plugin code, which will be automatically loaded by senpy.
|
||||||
|
* **version**
|
||||||
|
* extra_params: used to specify parameters that the plugin accepts that are not already part of the senpy API. Those parameters may be required, and have aliased names. For instance:
|
||||||
|
|
||||||
.. code:: python
|
.. code:: yaml
|
||||||
|
|
||||||
|
extra_params:
|
||||||
|
hello_param:
|
||||||
|
aliases: # required
|
||||||
|
- hello_param
|
||||||
|
- hello
|
||||||
|
required: true
|
||||||
|
default: Hi you
|
||||||
|
values:
|
||||||
|
- Hi you
|
||||||
|
- Hello y'all
|
||||||
|
- Howdy
|
||||||
|
|
||||||
|
Parameter validation will fail if a required parameter without a default has not been provided, or if the definition includes a set of values and the provided one does not match one of them.
|
||||||
|
|
||||||
|
|
||||||
|
A complete example:
|
||||||
|
|
||||||
|
.. code:: yaml
|
||||||
|
|
||||||
|
name: <Name of the plugin>
|
||||||
|
module: <Python file>
|
||||||
|
version: 0.1
|
||||||
|
|
||||||
|
And the json equivalent:
|
||||||
|
|
||||||
|
.. code:: json
|
||||||
|
|
||||||
{
|
{
|
||||||
"name" : "senpyPlugin",
|
"name": "<Name of the plugin>",
|
||||||
"module" : "{python code file}"
|
"module": "<Python file>",
|
||||||
|
"version": "0.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
name: senpyPlugin
|
|
||||||
module: {python code file}
|
|
||||||
|
|
||||||
Plugins Code
|
Plugins Code
|
||||||
=================
|
============
|
||||||
|
|
||||||
The basic methods in a plugin are:
|
The basic methods in a plugin are:
|
||||||
|
|
||||||
* __init__
|
* __init__
|
||||||
* activate: used to load memory-hungry resources
|
* activate: used to load memory-hungry resources
|
||||||
* deactivate: used to free up resources
|
* deactivate: used to free up resources
|
||||||
* analyse: called in every user requests. It takes in the parameters supplied by a user and should return a senpy Response.
|
* analyse_entry: called in every user requests. It takes in the parameters supplied by a user and should yield one or more ``Entry`` objects.
|
||||||
|
|
||||||
Plugins are loaded asynchronously, so don't worry if the activate method takes too long. The plugin will be marked as activated once it is finished executing the method.
|
Plugins are loaded asynchronously, so don't worry if the activate method takes too long. The plugin will be marked as activated once it is finished executing the method.
|
||||||
|
|
||||||
|
|
||||||
|
Example plugin
|
||||||
|
==============
|
||||||
|
|
||||||
|
In this section, we will implement a basic sentiment analysis plugin.
|
||||||
|
To determine the polarity of each entry, the plugin will compare the length of the string to a threshold.
|
||||||
|
This threshold will be included in the definition file.
|
||||||
|
|
||||||
|
The definition file would look like this:
|
||||||
|
|
||||||
|
.. code:: yaml
|
||||||
|
|
||||||
|
name: helloworld
|
||||||
|
module: helloworld
|
||||||
|
version: 0.0
|
||||||
|
threshold: 10
|
||||||
|
|
||||||
|
|
||||||
|
Now, in a file named ``helloworld.py``:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
#!/bin/env python
|
||||||
|
#helloworld.py
|
||||||
|
|
||||||
|
from senpy.plugins import SenpyPlugin
|
||||||
|
from senpy.models import Sentiment
|
||||||
|
|
||||||
|
|
||||||
|
class HelloWorld(SenpyPlugin):
|
||||||
|
|
||||||
|
def analyse_entry(entry, params):
|
||||||
|
'''Basically do nothing with each entry'''
|
||||||
|
|
||||||
|
sentiment = Sentiment()
|
||||||
|
if len(entry.text) < self.threshold:
|
||||||
|
sentiment['marl:hasPolarity'] = 'marl:Positive'
|
||||||
|
else:
|
||||||
|
sentiment['marl:hasPolarity'] = 'marl:Negative'
|
||||||
|
entry.sentiments.append(sentiment)
|
||||||
|
yield entry
|
||||||
|
|
||||||
|
|
||||||
F.A.Q.
|
F.A.Q.
|
||||||
======
|
======
|
||||||
|
Why does the analyse function yield instead of return?
|
||||||
|
??????????????????????????????????????????????????????
|
||||||
|
|
||||||
|
This is so that plugins may add new entries to the response or filter some of them.
|
||||||
|
For instance, a `context detection` plugin may add a new entry for each context in the original entry.
|
||||||
|
On the other hand, a conveersion plugin may leave out those entries that do not contain relevant information.
|
||||||
|
|
||||||
|
|
||||||
If I'm using a classifier, where should I train it?
|
If I'm using a classifier, where should I train it?
|
||||||
???????????????????????????????????????????????????
|
???????????????????????????????????????????????????
|
||||||
|
|
||||||
@@ -78,17 +159,17 @@ This example ilustrate how to implement the Sentiment140 service as a plugin in
|
|||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
class Sentiment140Plugin(SentimentPlugin):
|
class Sentiment140Plugin(SentimentPlugin):
|
||||||
def analyse(self, **params):
|
def analyse_entry(self, entry, params):
|
||||||
|
text = entry.text
|
||||||
lang = params.get("language", "auto")
|
lang = params.get("language", "auto")
|
||||||
res = requests.post("http://www.sentiment140.com/api/bulkClassifyJson",
|
res = requests.post("http://www.sentiment140.com/api/bulkClassifyJson",
|
||||||
json.dumps({"language": lang,
|
json.dumps({"language": lang,
|
||||||
"data": [{"text": params["input"]}]
|
"data": [{"text": text}]
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
p = params.get("prefix", None)
|
p = params.get("prefix", None)
|
||||||
response = Results(prefix=p)
|
|
||||||
polarity_value = self.maxPolarityValue*int(res.json()["data"][0]
|
polarity_value = self.maxPolarityValue*int(res.json()["data"][0]
|
||||||
["polarity"]) * 0.25
|
["polarity"]) * 0.25
|
||||||
polarity = "marl:Neutral"
|
polarity = "marl:Neutral"
|
||||||
@@ -98,18 +179,13 @@ This example ilustrate how to implement the Sentiment140 service as a plugin in
|
|||||||
elif polarity_value < neutral_value:
|
elif polarity_value < neutral_value:
|
||||||
polarity = "marl:Negative"
|
polarity = "marl:Negative"
|
||||||
|
|
||||||
entry = Entry(id="Entry0",
|
|
||||||
nif__isString=params["input"])
|
|
||||||
sentiment = Sentiment(id="Sentiment0",
|
sentiment = Sentiment(id="Sentiment0",
|
||||||
prefix=p,
|
prefix=p,
|
||||||
marl__hasPolarity=polarity,
|
marl__hasPolarity=polarity,
|
||||||
marl__polarityValue=polarity_value)
|
marl__polarityValue=polarity_value)
|
||||||
sentiment.prov__wasGeneratedBy = self.id
|
sentiment.prov__wasGeneratedBy = self.id
|
||||||
entry.sentiments = []
|
|
||||||
entry.sentiments.append(sentiment)
|
entry.sentiments.append(sentiment)
|
||||||
entry.language = lang
|
yield entry
|
||||||
response.entries.append(entry)
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
Where can I define extra parameters to be introduced in the request to my plugin?
|
Where can I define extra parameters to be introduced in the request to my plugin?
|
||||||
@@ -143,9 +219,9 @@ The extraction of this paremeter is used in the analyse method of the Plugin int
|
|||||||
Where can I set up variables for using them in my plugin?
|
Where can I set up variables for using them in my plugin?
|
||||||
?????????????????????????????????????????????????????????
|
?????????????????????????????????????????????????????????
|
||||||
|
|
||||||
You can add these variables in the definition file with the extracture of attribute-value pair.
|
You can add these variables in the definition file with the structure of attribute-value pairs.
|
||||||
|
|
||||||
Once you have added your variables, the next step is to extract them into the plugin. The plugin's __init__ method has a parameter called `info` where you can extract the values of the variables. This info parameter has the structure of a python dictionary.
|
Every field added to the definition file is available to the plugin instance.
|
||||||
|
|
||||||
Can I activate a DEBUG mode for my plugin?
|
Can I activate a DEBUG mode for my plugin?
|
||||||
???????????????????????????????????????????
|
???????????????????????????????????????????
|
||||||
@@ -154,7 +230,15 @@ You can activate the DEBUG mode by the command-line tool using the option -d.
|
|||||||
|
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
python -m senpy -d
|
senpy -d
|
||||||
|
|
||||||
|
|
||||||
|
Additionally, with the ``--pdb`` option you will be dropped into a pdb post mortem shell if an exception is raised.
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
senpy --pdb
|
||||||
|
|
||||||
|
|
||||||
Where can I find more code examples?
|
Where can I find more code examples?
|
||||||
????????????????????????????????????
|
????????????????????????????????????
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
Schema Examples
|
Schema Examples
|
||||||
===============
|
===============
|
||||||
All the examples in this page use the schema defined in :ref:`schema`.
|
All the examples in this page use the :download:`the main schema <_static/schemas/definitions.json>`.
|
||||||
|
|
||||||
Simple NIF annotation
|
Simple NIF annotation
|
||||||
---------------------
|
---------------------
|
||||||
|
@@ -1,12 +1,11 @@
|
|||||||
Flask>=0.10.1
|
Flask>=0.10.1
|
||||||
gunicorn>=19.0.0
|
|
||||||
requests>=2.4.1
|
requests>=2.4.1
|
||||||
GitPython>=0.3.2.RC1
|
|
||||||
gevent>=1.1rc4
|
gevent>=1.1rc4
|
||||||
PyLD>=0.6.5
|
PyLD>=0.6.5
|
||||||
Flask-Testing>=0.4.2
|
|
||||||
six
|
six
|
||||||
future
|
future
|
||||||
jsonschema
|
jsonschema
|
||||||
jsonref
|
jsonref
|
||||||
PyYAML
|
PyYAML
|
||||||
|
rdflib
|
||||||
|
rdflib-jsonld
|
||||||
|
@@ -1 +0,0 @@
|
|||||||
0.5.7
|
|
@@ -17,9 +17,13 @@
|
|||||||
"""
|
"""
|
||||||
Sentiment analysis server in Python
|
Sentiment analysis server in Python
|
||||||
"""
|
"""
|
||||||
|
from __future__ import print_function
|
||||||
|
from .version import __version__
|
||||||
|
|
||||||
import os
|
import logging
|
||||||
VFILE = os.path.join(os.path.dirname(__file__), "VERSION")
|
|
||||||
|
|
||||||
with open(VFILE, 'r') as f:
|
logger = logging.getLogger(__name__)
|
||||||
__version__ = f.read().strip()
|
|
||||||
|
logger.info('Using senpy version: {}'.format(__version__))
|
||||||
|
|
||||||
|
__all__ = ['api', 'blueprints', 'cli', 'extensions', 'models', 'plugins']
|
||||||
|
@@ -24,9 +24,9 @@ from flask import Flask
|
|||||||
from senpy.extensions import Senpy
|
from senpy.extensions import Senpy
|
||||||
from gevent.wsgi import WSGIServer
|
from gevent.wsgi import WSGIServer
|
||||||
from gevent.monkey import patch_all
|
from gevent.monkey import patch_all
|
||||||
import gevent
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
import argparse
|
import argparse
|
||||||
import senpy
|
import senpy
|
||||||
|
|
||||||
@@ -34,44 +34,78 @@ patch_all(thread=False)
|
|||||||
|
|
||||||
SERVER_PORT = os.environ.get("PORT", 5000)
|
SERVER_PORT = os.environ.get("PORT", 5000)
|
||||||
|
|
||||||
|
|
||||||
|
def info(type, value, tb):
|
||||||
|
if hasattr(sys, 'ps1') or not sys.stderr.isatty():
|
||||||
|
# we are in interactive mode or we don't have a tty-like
|
||||||
|
# device, so we call the default hook
|
||||||
|
sys.__excepthook__(type, value, tb)
|
||||||
|
else:
|
||||||
|
import traceback
|
||||||
|
import pdb
|
||||||
|
# we are NOT in interactive mode, print the exception...
|
||||||
|
traceback.print_exception(type, value, tb)
|
||||||
|
print
|
||||||
|
# ...then start the debugger in post-mortem mode.
|
||||||
|
# pdb.pm() # deprecated
|
||||||
|
pdb.post_mortem(tb) # more "modern"
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(description='Run a Senpy server')
|
parser = argparse.ArgumentParser(description='Run a Senpy server')
|
||||||
parser.add_argument('--level',
|
parser.add_argument(
|
||||||
|
'--level',
|
||||||
'-l',
|
'-l',
|
||||||
metavar='logging_level',
|
metavar='logging_level',
|
||||||
type=str,
|
type=str,
|
||||||
default="INFO",
|
default="INFO",
|
||||||
help='Logging level')
|
help='Logging level')
|
||||||
parser.add_argument('--debug',
|
parser.add_argument(
|
||||||
|
'--debug',
|
||||||
'-d',
|
'-d',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
default=False,
|
default=False,
|
||||||
help='Run the application in debug mode')
|
help='Run the application in debug mode')
|
||||||
parser.add_argument('--default-plugins',
|
parser.add_argument(
|
||||||
|
'--default-plugins',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
default=False,
|
default=False,
|
||||||
help='Load the default plugins')
|
help='Load the default plugins')
|
||||||
parser.add_argument('--host',
|
parser.add_argument(
|
||||||
|
'--host',
|
||||||
type=str,
|
type=str,
|
||||||
default="127.0.0.1",
|
default="0.0.0.0",
|
||||||
help='Use 0.0.0.0 to accept requests from any host.')
|
help='Use 0.0.0.0 to accept requests from any host.')
|
||||||
parser.add_argument('--port',
|
parser.add_argument(
|
||||||
|
'--port',
|
||||||
'-p',
|
'-p',
|
||||||
type=int,
|
type=int,
|
||||||
default=SERVER_PORT,
|
default=SERVER_PORT,
|
||||||
help='Port to listen on.')
|
help='Port to listen on.')
|
||||||
parser.add_argument('--plugins-folder',
|
parser.add_argument(
|
||||||
|
'--plugins-folder',
|
||||||
'-f',
|
'-f',
|
||||||
type=str,
|
type=str,
|
||||||
default='plugins',
|
default='plugins',
|
||||||
help='Where to look for plugins.')
|
help='Where to look for plugins.')
|
||||||
|
parser.add_argument(
|
||||||
|
'--only-install',
|
||||||
|
'-i',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help='Do not run a server, only install plugin dependencies')
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
logging.basicConfig()
|
logging.basicConfig()
|
||||||
rl = logging.getLogger()
|
rl = logging.getLogger()
|
||||||
rl.setLevel(getattr(logging, args.level))
|
rl.setLevel(getattr(logging, args.level))
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.debug = args.debug
|
app.debug = args.debug
|
||||||
|
if args.debug:
|
||||||
|
sys.excepthook = info
|
||||||
sp = Senpy(app, args.plugins_folder, default_plugins=args.default_plugins)
|
sp = Senpy(app, args.plugins_folder, default_plugins=args.default_plugins)
|
||||||
|
if args.only_install:
|
||||||
|
sp.install_deps()
|
||||||
|
return
|
||||||
sp.activate_all()
|
sp.activate_all()
|
||||||
http_server = WSGIServer((args.host, args.port), app)
|
http_server = WSGIServer((args.host, args.port), app)
|
||||||
try:
|
try:
|
||||||
@@ -80,8 +114,10 @@ def main():
|
|||||||
args.port))
|
args.port))
|
||||||
http_server.serve_forever()
|
http_server.serve_forever()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
http_server.stop()
|
|
||||||
print('Bye!')
|
print('Bye!')
|
||||||
|
http_server.stop()
|
||||||
|
sp.deactivate_all()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
49
senpy/api.py
49
senpy/api.py
@@ -1,13 +1,37 @@
|
|||||||
from future.utils import iteritems
|
from future.utils import iteritems
|
||||||
|
from .models import Error
|
||||||
import logging
|
import logging
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
from .models import Error
|
|
||||||
|
|
||||||
API_PARAMS = {
|
API_PARAMS = {
|
||||||
"algorithm": {
|
"algorithm": {
|
||||||
"aliases": ["algorithm", "a", "algo"],
|
"aliases": ["algorithm", "a", "algo"],
|
||||||
"required": False,
|
"required": False,
|
||||||
|
},
|
||||||
|
"outformat": {
|
||||||
|
"@id": "outformat",
|
||||||
|
"aliases": ["outformat", "o"],
|
||||||
|
"default": "json-ld",
|
||||||
|
"required": True,
|
||||||
|
"options": ["json-ld", "turtle"],
|
||||||
|
},
|
||||||
|
"expanded-jsonld": {
|
||||||
|
"@id": "expanded-jsonld",
|
||||||
|
"aliases": ["expanded", "expanded-jsonld"],
|
||||||
|
"required": True,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"emotionModel": {
|
||||||
|
"@id": "emotionModel",
|
||||||
|
"aliases": ["emotionModel", "emoModel"],
|
||||||
|
"required": False
|
||||||
|
},
|
||||||
|
"conversion": {
|
||||||
|
"@id": "conversion",
|
||||||
|
"description": "How to show the elements that have (not) been converted",
|
||||||
|
"required": True,
|
||||||
|
"options": ["filtered", "nested", "full"],
|
||||||
|
"default": "full"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,13 +72,6 @@ NIF_PARAMS = {
|
|||||||
"default": "direct",
|
"default": "direct",
|
||||||
"options": ["direct", "url", "file"],
|
"options": ["direct", "url", "file"],
|
||||||
},
|
},
|
||||||
"outformat": {
|
|
||||||
"@id": "outformat",
|
|
||||||
"aliases": ["outformat", "o"],
|
|
||||||
"default": "json-ld",
|
|
||||||
"required": False,
|
|
||||||
"options": ["json-ld"],
|
|
||||||
},
|
|
||||||
"language": {
|
"language": {
|
||||||
"@id": "language",
|
"@id": "language",
|
||||||
"aliases": ["language", "l"],
|
"aliases": ["language", "l"],
|
||||||
@@ -77,12 +94,12 @@ NIF_PARAMS = {
|
|||||||
|
|
||||||
|
|
||||||
def parse_params(indict, spec=NIF_PARAMS):
|
def parse_params(indict, spec=NIF_PARAMS):
|
||||||
outdict = {}
|
logger.debug("Parsing: {}\n{}".format(indict, spec))
|
||||||
|
outdict = indict.copy()
|
||||||
wrong_params = {}
|
wrong_params = {}
|
||||||
for param, options in iteritems(spec):
|
for param, options in iteritems(spec):
|
||||||
if param[0] != "@": # Exclude json-ld properties
|
if param[0] != "@": # Exclude json-ld properties
|
||||||
logger.debug("Param: %s - Options: %s", param, options)
|
for alias in options.get("aliases", []):
|
||||||
for alias in options["aliases"]:
|
|
||||||
if alias in indict:
|
if alias in indict:
|
||||||
outdict[param] = indict[alias]
|
outdict[param] = indict[alias]
|
||||||
if param not in outdict:
|
if param not in outdict:
|
||||||
@@ -96,10 +113,12 @@ def parse_params(indict, spec=NIF_PARAMS):
|
|||||||
outdict[param] not in spec[param]["options"]:
|
outdict[param] not in spec[param]["options"]:
|
||||||
wrong_params[param] = spec[param]
|
wrong_params[param] = spec[param]
|
||||||
if wrong_params:
|
if wrong_params:
|
||||||
message = Error(status=404,
|
logger.debug("Error parsing: %s", wrong_params)
|
||||||
|
message = Error(
|
||||||
|
status=400,
|
||||||
message="Missing or invalid parameters",
|
message="Missing or invalid parameters",
|
||||||
parameters=outdict,
|
parameters=outdict,
|
||||||
errors={param: error for param, error in
|
errors={param: error
|
||||||
iteritems(wrong_params)})
|
for param, error in iteritems(wrong_params)})
|
||||||
raise message
|
raise message
|
||||||
return outdict
|
return outdict
|
||||||
|
@@ -17,18 +17,21 @@
|
|||||||
"""
|
"""
|
||||||
Blueprints for Senpy
|
Blueprints for Senpy
|
||||||
"""
|
"""
|
||||||
from flask import Blueprint, request, current_app, render_template, url_for, jsonify
|
from flask import (Blueprint, request, current_app, render_template, url_for,
|
||||||
|
jsonify)
|
||||||
from .models import Error, Response, Plugins, read_schema
|
from .models import Error, Response, Plugins, read_schema
|
||||||
from .api import NIF_PARAMS, WEB_PARAMS, parse_params
|
from .api import WEB_PARAMS, API_PARAMS, parse_params
|
||||||
|
from .version import __version__
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
import json
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
api_blueprint = Blueprint("api", __name__)
|
api_blueprint = Blueprint("api", __name__)
|
||||||
demo_blueprint = Blueprint("demo", __name__)
|
demo_blueprint = Blueprint("demo", __name__)
|
||||||
|
ns_blueprint = Blueprint("ns", __name__)
|
||||||
|
|
||||||
|
|
||||||
def get_params(req):
|
def get_params(req):
|
||||||
if req.method == 'POST':
|
if req.method == 'POST':
|
||||||
@@ -42,11 +45,22 @@ def get_params(req):
|
|||||||
|
|
||||||
@demo_blueprint.route('/')
|
@demo_blueprint.route('/')
|
||||||
def index():
|
def index():
|
||||||
return render_template("index.html")
|
return render_template("index.html", version=__version__)
|
||||||
|
|
||||||
|
|
||||||
@api_blueprint.route('/contexts/<entity>.jsonld')
|
@api_blueprint.route('/contexts/<entity>.jsonld')
|
||||||
def context(entity="context"):
|
def context(entity="context"):
|
||||||
return jsonify({"@context": Response.context})
|
context = Response._context
|
||||||
|
context['@vocab'] = url_for('ns.index', _external=True)
|
||||||
|
return jsonify({"@context": context})
|
||||||
|
|
||||||
|
|
||||||
|
@ns_blueprint.route('/') # noqa: F811
|
||||||
|
def index():
|
||||||
|
context = Response._context
|
||||||
|
context['@vocab'] = url_for('.ns', _external=True)
|
||||||
|
return jsonify({"@context": context})
|
||||||
|
|
||||||
|
|
||||||
@api_blueprint.route('/schemas/<schema>')
|
@api_blueprint.route('/schemas/<schema>')
|
||||||
def schema(schema="definitions"):
|
def schema(schema="definitions"):
|
||||||
@@ -55,30 +69,47 @@ def schema(schema="definitions"):
|
|||||||
except Exception: # Should be FileNotFoundError, but it's missing from py2
|
except Exception: # Should be FileNotFoundError, but it's missing from py2
|
||||||
return Error(message="Schema not found", status=404).flask()
|
return Error(message="Schema not found", status=404).flask()
|
||||||
|
|
||||||
|
|
||||||
def basic_api(f):
|
def basic_api(f):
|
||||||
@wraps(f)
|
@wraps(f)
|
||||||
def decorated_function(*args, **kwargs):
|
def decorated_function(*args, **kwargs):
|
||||||
|
raw_params = get_params(request)
|
||||||
|
headers = {'X-ORIGINAL-PARAMS': raw_params}
|
||||||
|
# Get defaults
|
||||||
|
web_params = parse_params({}, spec=WEB_PARAMS)
|
||||||
|
api_params = parse_params({}, spec=API_PARAMS)
|
||||||
|
|
||||||
|
outformat = 'json-ld'
|
||||||
|
try:
|
||||||
print('Getting request:')
|
print('Getting request:')
|
||||||
print(request)
|
print(request)
|
||||||
raw_params = get_params(request)
|
|
||||||
web_params = parse_params(raw_params, spec=WEB_PARAMS)
|
web_params = parse_params(raw_params, spec=WEB_PARAMS)
|
||||||
|
api_params = parse_params(raw_params, spec=API_PARAMS)
|
||||||
if hasattr(request, 'params'):
|
if hasattr(request, 'params'):
|
||||||
request.params.update(raw_params)
|
request.params.update(api_params)
|
||||||
else:
|
else:
|
||||||
request.params = raw_params
|
request.params = api_params
|
||||||
try:
|
|
||||||
response = f(*args, **kwargs)
|
response = f(*args, **kwargs)
|
||||||
except Error as ex:
|
except Error as ex:
|
||||||
response = ex
|
response = ex
|
||||||
in_headers = web_params["inHeaders"] != "0"
|
|
||||||
headers = {'X-ORIGINAL-PARAMS': raw_params}
|
in_headers = web_params['inHeaders'] != "0"
|
||||||
return response.flask(in_headers=in_headers,
|
expanded = api_params['expanded-jsonld']
|
||||||
|
outformat = api_params['outformat']
|
||||||
|
|
||||||
|
return response.flask(
|
||||||
|
in_headers=in_headers,
|
||||||
headers=headers,
|
headers=headers,
|
||||||
context_uri=url_for('api.context', entity=type(response).__name__,
|
prefix=url_for('.api', _external=True),
|
||||||
_external=True))
|
context_uri=url_for('api.context',
|
||||||
|
entity=type(response).__name__,
|
||||||
|
_external=True),
|
||||||
|
outformat=outformat,
|
||||||
|
expanded=expanded)
|
||||||
|
|
||||||
return decorated_function
|
return decorated_function
|
||||||
|
|
||||||
|
|
||||||
@api_blueprint.route('/', methods=['POST', 'GET'])
|
@api_blueprint.route('/', methods=['POST', 'GET'])
|
||||||
@basic_api
|
@basic_api
|
||||||
def api():
|
def api():
|
||||||
@@ -93,32 +124,17 @@ def plugins():
|
|||||||
dic = Plugins(plugins=list(sp.plugins.values()))
|
dic = Plugins(plugins=list(sp.plugins.values()))
|
||||||
return dic
|
return dic
|
||||||
|
|
||||||
|
|
||||||
@api_blueprint.route('/plugins/<plugin>/', methods=['POST', 'GET'])
|
@api_blueprint.route('/plugins/<plugin>/', methods=['POST', 'GET'])
|
||||||
@api_blueprint.route('/plugins/<plugin>/<action>', methods=['POST', 'GET'])
|
|
||||||
@basic_api
|
@basic_api
|
||||||
def plugin(plugin=None, action="list"):
|
def plugin(plugin=None):
|
||||||
filt = {}
|
|
||||||
sp = current_app.senpy
|
sp = current_app.senpy
|
||||||
plugs = sp.filter_plugins(name=plugin)
|
|
||||||
if plugin == 'default' and sp.default_plugin:
|
if plugin == 'default' and sp.default_plugin:
|
||||||
response = sp.default_plugin
|
return sp.default_plugin
|
||||||
plugin = response.name
|
plugins = sp.filter_plugins(
|
||||||
elif plugin in sp.plugins:
|
id='plugins/{}'.format(plugin)) or sp.filter_plugins(name=plugin)
|
||||||
response = sp.plugins[plugin]
|
if plugins:
|
||||||
|
response = list(plugins.values())[0]
|
||||||
else:
|
else:
|
||||||
return Error(message="Plugin not found", status=404)
|
return Error(message="Plugin not found", status=404)
|
||||||
if action == "list":
|
|
||||||
return response
|
return response
|
||||||
method = "{}_plugin".format(action)
|
|
||||||
if(hasattr(sp, method)):
|
|
||||||
getattr(sp, method)(plugin)
|
|
||||||
return Response(message="Ok")
|
|
||||||
else:
|
|
||||||
return Error(message="action '{}' not allowed".format(action))
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
import config
|
|
||||||
|
|
||||||
app.register_blueprint(api_blueprint)
|
|
||||||
app.debug = config.DEBUG
|
|
||||||
app.run(host='0.0.0.0', port=5000)
|
|
||||||
|
@@ -3,6 +3,7 @@ from .models import Error
|
|||||||
from .api import parse_params, CLI_PARAMS
|
from .api import parse_params, CLI_PARAMS
|
||||||
from .extensions import Senpy
|
from .extensions import Senpy
|
||||||
|
|
||||||
|
|
||||||
def argv_to_dict(argv):
|
def argv_to_dict(argv):
|
||||||
'''Turns parameters in the form of '--key value' into a dict {'key': 'value'}
|
'''Turns parameters in the form of '--key value' into a dict {'key': 'value'}
|
||||||
'''
|
'''
|
||||||
@@ -18,6 +19,7 @@ def argv_to_dict(argv):
|
|||||||
cli_dict[key] = value
|
cli_dict[key] = value
|
||||||
return cli_dict
|
return cli_dict
|
||||||
|
|
||||||
|
|
||||||
def parse_cli(argv):
|
def parse_cli(argv):
|
||||||
cli_dict = argv_to_dict(argv)
|
cli_dict = argv_to_dict(argv)
|
||||||
cli_params = parse_params(cli_dict, spec=CLI_PARAMS)
|
cli_params = parse_params(cli_dict, spec=CLI_PARAMS)
|
||||||
@@ -34,6 +36,7 @@ def main_function(argv):
|
|||||||
res = sp.analyse(**cli_dict)
|
res = sp.analyse(**cli_dict)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
'''This method is the entrypoint for the CLI (as configured un setup.py)
|
'''This method is the entrypoint for the CLI (as configured un setup.py)
|
||||||
'''
|
'''
|
||||||
@@ -47,4 +50,3 @@ def main():
|
|||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
35
senpy/client.py
Normal file
35
senpy/client.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import requests
|
||||||
|
import logging
|
||||||
|
from . import models
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Client(object):
|
||||||
|
def __init__(self, endpoint):
|
||||||
|
self.endpoint = endpoint
|
||||||
|
|
||||||
|
def analyse(self, input, method='GET', **kwargs):
|
||||||
|
return self.request('/', method=method, input=input, **kwargs)
|
||||||
|
|
||||||
|
def request(self, path=None, method='GET', **params):
|
||||||
|
url = '{}{}'.format(self.endpoint, path)
|
||||||
|
response = requests.request(method=method, url=url, params=params)
|
||||||
|
try:
|
||||||
|
resp = models.from_dict(response.json())
|
||||||
|
resp.validate(resp)
|
||||||
|
return resp
|
||||||
|
except Exception as ex:
|
||||||
|
logger.error(('There seems to be a problem with the response:\n'
|
||||||
|
'\tURL: {url}\n'
|
||||||
|
'\tError: {error}\n'
|
||||||
|
'\t\n'
|
||||||
|
'#### Response:\n'
|
||||||
|
'\tCode: {code}'
|
||||||
|
'\tContent: {content}'
|
||||||
|
'\n').format(
|
||||||
|
error=ex,
|
||||||
|
url=url,
|
||||||
|
code=response.status_code,
|
||||||
|
content=response.content))
|
||||||
|
raise ex
|
@@ -1,18 +1,16 @@
|
|||||||
"""
|
"""
|
||||||
|
Main class for Senpy.
|
||||||
|
It orchestrates plugin (de)activation and analysis.
|
||||||
"""
|
"""
|
||||||
from future import standard_library
|
from future import standard_library
|
||||||
standard_library.install_aliases()
|
standard_library.install_aliases()
|
||||||
import gevent
|
|
||||||
from gevent import monkey
|
|
||||||
monkey.patch_all()
|
|
||||||
|
|
||||||
from .plugins import SenpyPlugin, SentimentPlugin, EmotionPlugin
|
from .plugins import SentimentPlugin, SenpyPlugin
|
||||||
from .models import Error
|
from .models import Error, Entry, Results
|
||||||
from .blueprints import api_blueprint, demo_blueprint
|
from .blueprints import api_blueprint, demo_blueprint, ns_blueprint
|
||||||
from .api import API_PARAMS, NIF_PARAMS, parse_params
|
from .api import API_PARAMS, NIF_PARAMS, parse_params
|
||||||
|
|
||||||
from git import Repo, InvalidGitRepositoryError
|
from threading import Thread
|
||||||
from functools import partial
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import fnmatch
|
import fnmatch
|
||||||
@@ -21,27 +19,32 @@ import sys
|
|||||||
import imp
|
import imp
|
||||||
import logging
|
import logging
|
||||||
import traceback
|
import traceback
|
||||||
import gevent
|
|
||||||
import yaml
|
import yaml
|
||||||
|
import pip
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Senpy(object):
|
class Senpy(object):
|
||||||
|
|
||||||
""" Default Senpy extension for Flask """
|
""" Default Senpy extension for Flask """
|
||||||
|
|
||||||
def __init__(self, app=None, plugin_folder="plugins", default_plugins=False):
|
def __init__(self,
|
||||||
|
app=None,
|
||||||
|
plugin_folder=".",
|
||||||
|
default_plugins=False):
|
||||||
self.app = app
|
self.app = app
|
||||||
|
|
||||||
self._search_folders = set()
|
self._search_folders = set()
|
||||||
self._plugin_list = []
|
self._plugin_list = []
|
||||||
self._outdated = True
|
self._outdated = True
|
||||||
|
self._default = None
|
||||||
|
|
||||||
self.add_folder(plugin_folder)
|
self.add_folder(plugin_folder)
|
||||||
if default_plugins:
|
if default_plugins:
|
||||||
base_folder = os.path.join(os.path.dirname(__file__), "plugins")
|
self.add_folder('plugins', from_root=True)
|
||||||
self.add_folder(base_folder)
|
else:
|
||||||
|
# Add only conversion plugins
|
||||||
|
self.add_folder(os.path.join('plugins', 'conversion'),
|
||||||
|
from_root=True)
|
||||||
|
|
||||||
if app is not None:
|
if app is not None:
|
||||||
self.init_app(app)
|
self.init_app(app)
|
||||||
@@ -60,9 +63,12 @@ class Senpy(object):
|
|||||||
else:
|
else:
|
||||||
app.teardown_request(self.teardown)
|
app.teardown_request(self.teardown)
|
||||||
app.register_blueprint(api_blueprint, url_prefix="/api")
|
app.register_blueprint(api_blueprint, url_prefix="/api")
|
||||||
|
app.register_blueprint(ns_blueprint, url_prefix="/ns")
|
||||||
app.register_blueprint(demo_blueprint, url_prefix="/")
|
app.register_blueprint(demo_blueprint, url_prefix="/")
|
||||||
|
|
||||||
def add_folder(self, folder):
|
def add_folder(self, folder, from_root=False):
|
||||||
|
if from_root:
|
||||||
|
folder = os.path.join(os.path.dirname(__file__), folder)
|
||||||
logger.debug("Adding folder: %s", folder)
|
logger.debug("Adding folder: %s", folder)
|
||||||
if os.path.isdir(folder):
|
if os.path.isdir(folder):
|
||||||
self._search_folders.add(folder)
|
self._search_folders.add(folder)
|
||||||
@@ -70,58 +76,140 @@ class Senpy(object):
|
|||||||
else:
|
else:
|
||||||
logger.debug("Not a folder: %s", folder)
|
logger.debug("Not a folder: %s", folder)
|
||||||
|
|
||||||
def analyse(self, **params):
|
def _find_plugin(self, params):
|
||||||
algo = None
|
|
||||||
logger.debug("analysing with params: {}".format(params))
|
|
||||||
api_params = parse_params(params, spec=API_PARAMS)
|
api_params = parse_params(params, spec=API_PARAMS)
|
||||||
|
algo = None
|
||||||
if "algorithm" in api_params and api_params["algorithm"]:
|
if "algorithm" in api_params and api_params["algorithm"]:
|
||||||
algo = api_params["algorithm"]
|
algo = api_params["algorithm"]
|
||||||
elif self.plugins:
|
elif self.plugins:
|
||||||
algo = self.default_plugin and self.default_plugin.name
|
algo = self.default_plugin and self.default_plugin.name
|
||||||
if not algo:
|
if not algo:
|
||||||
raise Error(status=404,
|
raise Error(
|
||||||
|
status=404,
|
||||||
message=("No plugins found."
|
message=("No plugins found."
|
||||||
" Please install one.").format(algo))
|
" Please install one.").format(algo))
|
||||||
if algo not in self.plugins:
|
if algo not in self.plugins:
|
||||||
logger.debug(("The algorithm '{}' is not valid\n"
|
logger.debug(("The algorithm '{}' is not valid\n"
|
||||||
"Valid algorithms: {}").format(algo,
|
"Valid algorithms: {}").format(algo,
|
||||||
self.plugins.keys()))
|
self.plugins.keys()))
|
||||||
raise Error(status=404,
|
raise Error(
|
||||||
message="The algorithm '{}' is not valid"
|
status=404,
|
||||||
.format(algo))
|
message="The algorithm '{}' is not valid".format(algo))
|
||||||
|
|
||||||
if not self.plugins[algo].is_activated:
|
if not self.plugins[algo].is_activated:
|
||||||
logger.debug("Plugin not activated: {}".format(algo))
|
logger.debug("Plugin not activated: {}".format(algo))
|
||||||
raise Error(status=400,
|
raise Error(
|
||||||
|
status=400,
|
||||||
message=("The algorithm '{}'"
|
message=("The algorithm '{}'"
|
||||||
" is not activated yet").format(algo))
|
" is not activated yet").format(algo))
|
||||||
plug = self.plugins[algo]
|
return self.plugins[algo]
|
||||||
|
|
||||||
|
def _get_params(self, params, plugin):
|
||||||
nif_params = parse_params(params, spec=NIF_PARAMS)
|
nif_params = parse_params(params, spec=NIF_PARAMS)
|
||||||
extra_params = plug.get('extra_params', {})
|
extra_params = plugin.get('extra_params', {})
|
||||||
specific_params = parse_params(params, spec=extra_params)
|
specific_params = parse_params(params, spec=extra_params)
|
||||||
nif_params.update(specific_params)
|
nif_params.update(specific_params)
|
||||||
|
return nif_params
|
||||||
|
|
||||||
|
def _get_entries(self, params):
|
||||||
|
entry = None
|
||||||
|
if params['informat'] == 'text':
|
||||||
|
entry = Entry(text=params['input'])
|
||||||
|
else:
|
||||||
|
raise NotImplemented('Only text input format implemented')
|
||||||
|
yield entry
|
||||||
|
|
||||||
|
def analyse(self, **api_params):
|
||||||
|
logger.debug("analysing with params: {}".format(api_params))
|
||||||
|
plugin = self._find_plugin(api_params)
|
||||||
|
nif_params = self._get_params(api_params, plugin)
|
||||||
|
resp = Results()
|
||||||
|
if 'with_parameters' in api_params:
|
||||||
|
resp.parameters = nif_params
|
||||||
try:
|
try:
|
||||||
resp = plug.analyse(**nif_params)
|
entries = []
|
||||||
resp.analysis.append(plug)
|
for i in self._get_entries(nif_params):
|
||||||
|
entries += list(plugin.analyse_entry(i, nif_params))
|
||||||
|
resp.entries = entries
|
||||||
|
self.convert_emotions(resp, plugin, nif_params)
|
||||||
|
resp.analysis.append(plugin.id)
|
||||||
logger.debug("Returning analysis result: {}".format(resp))
|
logger.debug("Returning analysis result: {}".format(resp))
|
||||||
|
except Error as ex:
|
||||||
|
logger.exception('Error returning analysis result')
|
||||||
|
resp = ex
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
|
logger.exception('Error returning analysis result')
|
||||||
resp = Error(message=str(ex), status=500)
|
resp = Error(message=str(ex), status=500)
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
|
def _conversion_candidates(self, fromModel, toModel):
|
||||||
|
candidates = self.filter_plugins(**{'@type': 'emotionConversionPlugin'})
|
||||||
|
for name, candidate in candidates.items():
|
||||||
|
for pair in candidate.onyx__doesConversion:
|
||||||
|
logging.debug(pair)
|
||||||
|
|
||||||
|
if pair['onyx:conversionFrom'] == fromModel \
|
||||||
|
and pair['onyx:conversionTo'] == toModel:
|
||||||
|
# logging.debug('Found candidate: {}'.format(candidate))
|
||||||
|
yield candidate
|
||||||
|
|
||||||
|
def convert_emotions(self, resp, plugin, params):
|
||||||
|
"""
|
||||||
|
Conversion of all emotions in a response.
|
||||||
|
In addition to converting from one model to another, it has
|
||||||
|
to include the conversion plugin to the analysis list.
|
||||||
|
Needless to say, this is far from an elegant solution, but it works.
|
||||||
|
@todo refactor and clean up
|
||||||
|
"""
|
||||||
|
fromModel = plugin.get('onyx:usesEmotionModel', None)
|
||||||
|
toModel = params.get('emotionModel', None)
|
||||||
|
output = params.get('conversion', None)
|
||||||
|
logger.debug('Asked for model: {}'.format(toModel))
|
||||||
|
logger.debug('Analysis plugin uses model: {}'.format(fromModel))
|
||||||
|
|
||||||
|
if not toModel:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
candidate = next(self._conversion_candidates(fromModel, toModel))
|
||||||
|
except StopIteration:
|
||||||
|
e = Error(('No conversion plugin found for: '
|
||||||
|
'{} -> {}'.format(fromModel, toModel)))
|
||||||
|
e.original_response = resp
|
||||||
|
e.parameters = params
|
||||||
|
raise e
|
||||||
|
newentries = []
|
||||||
|
for i in resp.entries:
|
||||||
|
if output == "full":
|
||||||
|
newemotions = i.emotions.copy()
|
||||||
|
else:
|
||||||
|
newemotions = []
|
||||||
|
for j in i.emotions:
|
||||||
|
for k in candidate.convert(j, fromModel, toModel, params):
|
||||||
|
k.prov__wasGeneratedBy = candidate.id
|
||||||
|
if output == 'nested':
|
||||||
|
k.prov__wasDerivedFrom = j
|
||||||
|
newemotions.append(k)
|
||||||
|
i.emotions = newemotions
|
||||||
|
newentries.append(i)
|
||||||
|
resp.entries = newentries
|
||||||
|
resp.analysis.append(candidate.id)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def default_plugin(self):
|
def default_plugin(self):
|
||||||
|
candidate = self._default
|
||||||
|
if not candidate:
|
||||||
candidates = self.filter_plugins(is_activated=True)
|
candidates = self.filter_plugins(is_activated=True)
|
||||||
if len(candidates) > 0:
|
if len(candidates) > 0:
|
||||||
candidate = list(candidates.values())[0]
|
candidate = list(candidates.values())[0]
|
||||||
logger.debug("Default: {}".format(candidate.name))
|
logger.debug("Default: {}".format(candidate))
|
||||||
return candidate
|
return candidate
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def parameters(self, algo):
|
@default_plugin.setter
|
||||||
return getattr(self.plugins.get(algo) or self.default_plugin,
|
def default_plugin(self, value):
|
||||||
"extra_params",
|
if isinstance(value, SenpyPlugin):
|
||||||
{})
|
self._default = value
|
||||||
|
else:
|
||||||
|
self._default = self.plugins[value]
|
||||||
|
|
||||||
def activate_all(self, sync=False):
|
def activate_all(self, sync=False):
|
||||||
ps = []
|
ps = []
|
||||||
@@ -145,103 +233,116 @@ class Senpy(object):
|
|||||||
try:
|
try:
|
||||||
plugin = self.plugins[plugin_name]
|
plugin = self.plugins[plugin_name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise Error(message="Plugin not found: {}".format(plugin_name),
|
raise Error(
|
||||||
status=404)
|
message="Plugin not found: {}".format(plugin_name), status=404)
|
||||||
|
|
||||||
logger.info("Activating plugin: {}".format(plugin.name))
|
logger.info("Activating plugin: {}".format(plugin.name))
|
||||||
|
|
||||||
def act():
|
def act():
|
||||||
|
success = False
|
||||||
try:
|
try:
|
||||||
plugin.activate()
|
plugin.activate()
|
||||||
logger.info("Plugin activated: {}".format(plugin.name))
|
msg = "Plugin activated: {}".format(plugin.name)
|
||||||
|
logger.info(msg)
|
||||||
|
success = True
|
||||||
|
self._set_active_plugin(plugin_name, success)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
logger.error("Error activating plugin {}: {}".format(plugin.name,
|
msg = "Error activating plugin {} - {} : \n\t{}".format(
|
||||||
ex))
|
plugin.name, ex, traceback.format_exc())
|
||||||
logger.error("Trace: {}".format(traceback.format_exc()))
|
logger.error(msg)
|
||||||
th = gevent.spawn(act)
|
raise Error(msg)
|
||||||
th.link_value(partial(self._set_active_plugin, plugin_name, True))
|
|
||||||
if sync:
|
if sync:
|
||||||
th.join()
|
act()
|
||||||
else:
|
else:
|
||||||
return th
|
th = Thread(target=act)
|
||||||
|
th.start()
|
||||||
|
|
||||||
def deactivate_plugin(self, plugin_name, sync=False):
|
def deactivate_plugin(self, plugin_name, sync=False):
|
||||||
try:
|
try:
|
||||||
plugin = self.plugins[plugin_name]
|
plugin = self.plugins[plugin_name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise Error(message="Plugin not found: {}".format(plugin_name),
|
raise Error(
|
||||||
status=404)
|
message="Plugin not found: {}".format(plugin_name), status=404)
|
||||||
|
|
||||||
|
self._set_active_plugin(plugin_name, False)
|
||||||
|
|
||||||
def deact():
|
def deact():
|
||||||
try:
|
try:
|
||||||
plugin.deactivate()
|
plugin.deactivate()
|
||||||
logger.info("Plugin deactivated: {}".format(plugin.name))
|
logger.info("Plugin deactivated: {}".format(plugin.name))
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
logger.error("Error deactivating plugin {}: {}".format(plugin.name,
|
logger.error(
|
||||||
ex))
|
"Error deactivating plugin {}: {}".format(plugin.name, ex))
|
||||||
logger.error("Trace: {}".format(traceback.format_exc()))
|
logger.error("Trace: {}".format(traceback.format_exc()))
|
||||||
|
|
||||||
th = gevent.spawn(deact)
|
|
||||||
th.link_value(partial(self._set_active_plugin, plugin_name, False))
|
|
||||||
if sync:
|
if sync:
|
||||||
th.join()
|
deact()
|
||||||
else:
|
else:
|
||||||
return th
|
th = Thread(target=deact)
|
||||||
|
th.start()
|
||||||
|
|
||||||
def reload_plugin(self, name):
|
@classmethod
|
||||||
logger.debug("Reloading {}".format(name))
|
def validate_info(cls, info):
|
||||||
plugin = self.plugins[name]
|
return all(x in info for x in ('name', 'module', 'version'))
|
||||||
try:
|
|
||||||
del self.plugins[name]
|
|
||||||
nplug = self._load_plugin(plugin.module, plugin.path)
|
|
||||||
self.plugins[nplug.name] = nplug
|
|
||||||
except Exception as ex:
|
|
||||||
logger.error('Error reloading {}: {}'.format(name, ex))
|
|
||||||
self.plugins[name] = plugin
|
|
||||||
|
|
||||||
@staticmethod
|
def install_deps(self):
|
||||||
def _load_plugin(root, filename):
|
for i in self.plugins.values():
|
||||||
logger.debug("Loading plugin: {}".format(filename))
|
self._install_deps(i._info)
|
||||||
fpath = os.path.join(root, filename)
|
|
||||||
with open(fpath, 'r') as f:
|
@classmethod
|
||||||
info = yaml.load(f)
|
def _install_deps(cls, info=None):
|
||||||
logger.debug("Info: {}".format(info))
|
requirements = info.get('requirements', [])
|
||||||
sys.path.append(root)
|
if requirements:
|
||||||
|
pip_args = []
|
||||||
|
pip_args.append('install')
|
||||||
|
pip_args.append('--use-wheel')
|
||||||
|
for req in requirements:
|
||||||
|
pip_args.append(req)
|
||||||
|
logger.info('Installing requirements: ' + str(requirements))
|
||||||
|
pip.main(pip_args)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _load_plugin_from_info(cls, info, root):
|
||||||
|
if not cls.validate_info(info):
|
||||||
|
logger.warn('The module info is not valid.\n\t{}'.format(info))
|
||||||
|
return None, None
|
||||||
module = info["module"]
|
module = info["module"]
|
||||||
name = info["name"]
|
name = info["name"]
|
||||||
|
sys.path.append(root)
|
||||||
(fp, pathname, desc) = imp.find_module(module, [root, ])
|
(fp, pathname, desc) = imp.find_module(module, [root, ])
|
||||||
try:
|
cls._install_deps(info)
|
||||||
tmp = imp.load_module(module, fp, pathname, desc)
|
tmp = imp.load_module(module, fp, pathname, desc)
|
||||||
sys.path.remove(root)
|
sys.path.remove(root)
|
||||||
candidate = None
|
candidate = None
|
||||||
for _, obj in inspect.getmembers(tmp):
|
for _, obj in inspect.getmembers(tmp):
|
||||||
if inspect.isclass(obj) and inspect.getmodule(obj) == tmp:
|
if inspect.isclass(obj) and inspect.getmodule(obj) == tmp:
|
||||||
logger.debug(("Found plugin class:"
|
logger.debug(("Found plugin class:"
|
||||||
" {}@{}").format(obj, inspect.getmodule(obj))
|
" {}@{}").format(obj, inspect.getmodule(obj)))
|
||||||
)
|
|
||||||
candidate = obj
|
candidate = obj
|
||||||
break
|
break
|
||||||
if not candidate:
|
if not candidate:
|
||||||
logger.debug("No valid plugin for: {}".format(filename))
|
logger.debug("No valid plugin for: {}".format(module))
|
||||||
return
|
return
|
||||||
module = candidate(info=info)
|
module = candidate(info=info)
|
||||||
try:
|
|
||||||
repo_path = root
|
|
||||||
module._repo = Repo(repo_path)
|
|
||||||
except InvalidGitRepositoryError:
|
|
||||||
module._repo = None
|
|
||||||
except Exception as ex:
|
|
||||||
logger.error("Exception importing {}: {}".format(filename, ex))
|
|
||||||
logger.error("Trace: {}".format(traceback.format_exc()))
|
|
||||||
return None, None
|
|
||||||
return name, module
|
return name, module
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _load_plugin(cls, root, filename):
|
||||||
|
fpath = os.path.join(root, filename)
|
||||||
|
logger.debug("Loading plugin: {}".format(fpath))
|
||||||
|
with open(fpath, 'r') as f:
|
||||||
|
info = yaml.load(f)
|
||||||
|
logger.debug("Info: {}".format(info))
|
||||||
|
return cls._load_plugin_from_info(info, root)
|
||||||
|
|
||||||
def _load_plugins(self):
|
def _load_plugins(self):
|
||||||
plugins = {}
|
plugins = {}
|
||||||
for search_folder in self._search_folders:
|
for search_folder in self._search_folders:
|
||||||
for root, dirnames, filenames in os.walk(search_folder):
|
for root, dirnames, filenames in os.walk(search_folder):
|
||||||
for filename in fnmatch.filter(filenames, '*.senpy'):
|
for filename in fnmatch.filter(filenames, '*.senpy'):
|
||||||
name, plugin = self._load_plugin(root, filename)
|
name, plugin = self._load_plugin(root, filename)
|
||||||
if plugin and name not in self._plugin_list:
|
if plugin and name:
|
||||||
plugins[name] = plugin
|
plugins[name] = plugin
|
||||||
|
|
||||||
self._outdated = False
|
self._outdated = False
|
||||||
@@ -262,9 +363,8 @@ class Senpy(object):
|
|||||||
|
|
||||||
def matches(plug):
|
def matches(plug):
|
||||||
res = all(getattr(plug, k, None) == v for (k, v) in kwargs.items())
|
res = all(getattr(plug, k, None) == v for (k, v) in kwargs.items())
|
||||||
logger.debug("matching {} with {}: {}".format(plug.name,
|
logger.debug(
|
||||||
kwargs,
|
"matching {} with {}: {}".format(plug.name, kwargs, res))
|
||||||
res))
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
if not kwargs:
|
if not kwargs:
|
||||||
@@ -274,5 +374,8 @@ class Senpy(object):
|
|||||||
|
|
||||||
def sentiment_plugins(self):
|
def sentiment_plugins(self):
|
||||||
""" Return only the sentiment plugins """
|
""" Return only the sentiment plugins """
|
||||||
return {p: plugin for p, plugin in self.plugins.items() if
|
return {
|
||||||
isinstance(plugin, SentimentPlugin)}
|
p: plugin
|
||||||
|
for p, plugin in self.plugins.items()
|
||||||
|
if isinstance(plugin, SentimentPlugin)
|
||||||
|
}
|
||||||
|
273
senpy/models.py
273
senpy/models.py
@@ -2,8 +2,8 @@
|
|||||||
Senpy Models.
|
Senpy Models.
|
||||||
|
|
||||||
This implementation should mirror the JSON schema definition.
|
This implementation should mirror the JSON schema definition.
|
||||||
For compatibility with Py3 and for easier debugging, this new version drops introspection
|
For compatibility with Py3 and for easier debugging, this new version drops
|
||||||
and adds all arguments to the models.
|
introspection and adds all arguments to the models.
|
||||||
'''
|
'''
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
from six import string_types
|
from six import string_types
|
||||||
@@ -12,34 +12,43 @@ import time
|
|||||||
import copy
|
import copy
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import logging
|
|
||||||
import jsonref
|
import jsonref
|
||||||
import jsonschema
|
import jsonschema
|
||||||
|
|
||||||
from flask import Response as FlaskResponse
|
from flask import Response as FlaskResponse
|
||||||
|
from pyld import jsonld
|
||||||
|
|
||||||
|
from rdflib import Graph
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
DEFINITIONS_FILE = 'definitions.json'
|
DEFINITIONS_FILE = 'definitions.json'
|
||||||
CONTEXT_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'schemas', 'context.jsonld')
|
CONTEXT_PATH = os.path.join(
|
||||||
|
os.path.dirname(os.path.realpath(__file__)), 'schemas', 'context.jsonld')
|
||||||
|
|
||||||
|
|
||||||
def get_schema_path(schema_file, absolute=False):
|
def get_schema_path(schema_file, absolute=False):
|
||||||
if absolute:
|
if absolute:
|
||||||
return os.path.realpath(schema_file)
|
return os.path.realpath(schema_file)
|
||||||
else:
|
else:
|
||||||
return os.path.join(os.path.dirname(os.path.realpath(__file__)), 'schemas', schema_file)
|
return os.path.join(
|
||||||
|
os.path.dirname(os.path.realpath(__file__)), 'schemas',
|
||||||
|
schema_file)
|
||||||
|
|
||||||
|
|
||||||
def read_schema(schema_file, absolute=False):
|
def read_schema(schema_file, absolute=False):
|
||||||
schema_path = get_schema_path(schema_file, absolute)
|
schema_path = get_schema_path(schema_file, absolute)
|
||||||
schema_uri = 'file://{}'.format(schema_path)
|
schema_uri = 'file://{}'.format(schema_path)
|
||||||
return jsonref.load(open(schema_path), base_uri=schema_uri)
|
with open(schema_path) as f:
|
||||||
|
return jsonref.load(f, base_uri=schema_uri)
|
||||||
|
|
||||||
|
|
||||||
base_schema = read_schema(DEFINITIONS_FILE)
|
base_schema = read_schema(DEFINITIONS_FILE)
|
||||||
logging.debug(base_schema)
|
|
||||||
|
|
||||||
class Context(dict):
|
class Context(dict):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def load(context):
|
def load(context):
|
||||||
logging.debug('Loading context: {}'.format(context))
|
logging.debug('Loading context: {}'.format(context))
|
||||||
@@ -61,14 +70,17 @@ class Context(dict):
|
|||||||
else:
|
else:
|
||||||
raise AttributeError('Please, provide a valid context')
|
raise AttributeError('Please, provide a valid context')
|
||||||
|
|
||||||
|
|
||||||
base_context = Context.load(CONTEXT_PATH)
|
base_context = Context.load(CONTEXT_PATH)
|
||||||
|
|
||||||
|
|
||||||
class SenpyMixin(object):
|
class SenpyMixin(object):
|
||||||
context = base_context["@context"]
|
_context = base_context["@context"]
|
||||||
|
|
||||||
def flask(self,
|
def flask(self,
|
||||||
in_headers=False,
|
in_headers=True,
|
||||||
headers=None,
|
headers=None,
|
||||||
|
outformat='json-ld',
|
||||||
**kwargs):
|
**kwargs):
|
||||||
"""
|
"""
|
||||||
Return the values and error to be used in flask.
|
Return the values and error to be used in flask.
|
||||||
@@ -76,21 +88,47 @@ class SenpyMixin(object):
|
|||||||
contexts if the plugin adds more aliases.
|
contexts if the plugin adds more aliases.
|
||||||
"""
|
"""
|
||||||
headers = headers or {}
|
headers = headers or {}
|
||||||
kwargs["with_context"] = True
|
kwargs["with_context"] = not in_headers
|
||||||
js = self.jsonld(**kwargs)
|
content, mimetype = self.serialize(format=outformat,
|
||||||
if in_headers:
|
with_mime=True,
|
||||||
url = js["@context"]
|
**kwargs)
|
||||||
del js["@context"]
|
|
||||||
|
if outformat == 'json-ld' and in_headers:
|
||||||
headers.update({
|
headers.update({
|
||||||
"Link": ('<%s>;'
|
"Link":
|
||||||
|
('<%s>;'
|
||||||
'rel="http://www.w3.org/ns/json-ld#context";'
|
'rel="http://www.w3.org/ns/json-ld#context";'
|
||||||
' type="application/ld+json"' % url)
|
' type="application/ld+json"' % kwargs.get('context_uri'))
|
||||||
})
|
})
|
||||||
return FlaskResponse(json.dumps(js, indent=2, sort_keys=True),
|
return FlaskResponse(
|
||||||
|
response=content,
|
||||||
status=getattr(self, "status", 200),
|
status=getattr(self, "status", 200),
|
||||||
headers=headers,
|
headers=headers,
|
||||||
mimetype="application/json")
|
mimetype=mimetype)
|
||||||
|
|
||||||
|
def serialize(self, format='json-ld', with_mime=False, **kwargs):
|
||||||
|
js = self.jsonld(**kwargs)
|
||||||
|
if format == 'json-ld':
|
||||||
|
content = json.dumps(js, indent=2, sort_keys=True)
|
||||||
|
mimetype = "application/json"
|
||||||
|
elif format in ['turtle', ]:
|
||||||
|
logger.debug(js)
|
||||||
|
content = json.dumps(js, indent=2, sort_keys=True)
|
||||||
|
g = Graph().parse(
|
||||||
|
data=content,
|
||||||
|
format='json-ld',
|
||||||
|
base=kwargs.get('prefix'),
|
||||||
|
context=self._context)
|
||||||
|
logger.debug(
|
||||||
|
'Parsing with prefix: {}'.format(kwargs.get('prefix')))
|
||||||
|
content = g.serialize(format='turtle').decode('utf-8')
|
||||||
|
mimetype = 'text/{}'.format(format)
|
||||||
|
else:
|
||||||
|
raise Error('Unknown outformat: {}'.format(format))
|
||||||
|
if with_mime:
|
||||||
|
return content, mimetype
|
||||||
|
else:
|
||||||
|
return content
|
||||||
|
|
||||||
def serializable(self):
|
def serializable(self):
|
||||||
def ser_or_down(item):
|
def ser_or_down(item):
|
||||||
@@ -106,36 +144,36 @@ class SenpyMixin(object):
|
|||||||
return list(ser_or_down(i) for i in item)
|
return list(ser_or_down(i) for i in item)
|
||||||
else:
|
else:
|
||||||
return item
|
return item
|
||||||
|
|
||||||
return ser_or_down(self._plain_dict())
|
return ser_or_down(self._plain_dict())
|
||||||
|
|
||||||
|
def jsonld(self,
|
||||||
def jsonld(self, with_context=True, context_uri=None):
|
with_context=True,
|
||||||
|
context_uri=None,
|
||||||
|
prefix=None,
|
||||||
|
expanded=False):
|
||||||
ser = self.serializable()
|
ser = self.serializable()
|
||||||
|
|
||||||
if with_context:
|
result = jsonld.compact(
|
||||||
context = []
|
ser,
|
||||||
|
self._context,
|
||||||
|
options={
|
||||||
|
'base': prefix,
|
||||||
|
'expandContext': self._context,
|
||||||
|
'senpy': prefix
|
||||||
|
})
|
||||||
if context_uri:
|
if context_uri:
|
||||||
context = context_uri
|
result['@context'] = context_uri
|
||||||
else:
|
if expanded:
|
||||||
context = self.context.copy()
|
result = jsonld.expand(
|
||||||
if hasattr(self, 'prefix'):
|
result, options={'base': prefix,
|
||||||
# This sets @base for the document, which will be used in
|
'expandContext': self._context})
|
||||||
# all relative URIs will. For example, if a uri is "Example" and
|
if not with_context:
|
||||||
# prefix =s "http://example.com", the absolute URI after expanding
|
del result['@context']
|
||||||
# with JSON-LD will be "http://example.com/Example"
|
return result
|
||||||
|
|
||||||
prefix_context = {"@base": self.prefix}
|
|
||||||
if isinstance(context, list):
|
|
||||||
context.append(prefix_context)
|
|
||||||
else:
|
|
||||||
context = [context, prefix_context]
|
|
||||||
ser["@context"] = context
|
|
||||||
return ser
|
|
||||||
|
|
||||||
|
|
||||||
def to_JSON(self, *args, **kwargs):
|
def to_JSON(self, *args, **kwargs):
|
||||||
js = json.dumps(self.jsonld(*args, **kwargs), indent=4,
|
js = json.dumps(self.jsonld(*args, **kwargs), indent=4, sort_keys=True)
|
||||||
sort_keys=True)
|
|
||||||
return js
|
return js
|
||||||
|
|
||||||
def validate(self, obj=None):
|
def validate(self, obj=None):
|
||||||
@@ -145,34 +183,38 @@ class SenpyMixin(object):
|
|||||||
obj = obj.jsonld()
|
obj = obj.jsonld()
|
||||||
jsonschema.validate(obj, self.schema)
|
jsonschema.validate(obj, self.schema)
|
||||||
|
|
||||||
class SenpyModel(SenpyMixin, dict):
|
def __str__(self):
|
||||||
|
return str(self.to_JSON())
|
||||||
|
|
||||||
|
|
||||||
|
class BaseModel(SenpyMixin, dict):
|
||||||
|
|
||||||
schema = base_schema
|
schema = base_schema
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.id = kwargs.pop('id', '{}_{}'.format(type(self).__name__,
|
if 'id' in kwargs:
|
||||||
time.time()))
|
self.id = kwargs.pop('id')
|
||||||
|
elif kwargs.pop('_auto_id', True):
|
||||||
|
self.id = '_:{}_{}'.format(type(self).__name__, time.time())
|
||||||
temp = dict(*args, **kwargs)
|
temp = dict(*args, **kwargs)
|
||||||
|
|
||||||
|
for obj in [
|
||||||
|
self.schema,
|
||||||
|
] + self.schema.get('allOf', []):
|
||||||
|
for k, v in obj.get('properties', {}).items():
|
||||||
|
if 'default' in v and k not in temp:
|
||||||
|
temp[k] = copy.deepcopy(v['default'])
|
||||||
|
|
||||||
for i in temp:
|
for i in temp:
|
||||||
nk = self._get_key(i)
|
nk = self._get_key(i)
|
||||||
if nk != i:
|
if nk != i:
|
||||||
temp[nk] = temp[i]
|
temp[nk] = temp[i]
|
||||||
del temp[i]
|
del temp[i]
|
||||||
|
try:
|
||||||
reqs = self.schema.get('required', [])
|
temp['@type'] = getattr(self, '@type')
|
||||||
for i in reqs:
|
except AttributeError:
|
||||||
if i not in temp:
|
logger.warn('Creating an instance of an unknown model')
|
||||||
prop = self.schema['properties'][i]
|
super(BaseModel, self).__init__(temp)
|
||||||
if 'default' in prop:
|
|
||||||
temp[i] = copy.deepcopy(prop['default'])
|
|
||||||
if 'context' in temp:
|
|
||||||
context = temp['context']
|
|
||||||
del temp['context']
|
|
||||||
self.__dict__['context'] = Context.load(context)
|
|
||||||
super(SenpyModel, self).__init__(temp)
|
|
||||||
|
|
||||||
|
|
||||||
def _get_key(self, key):
|
def _get_key(self, key):
|
||||||
key = key.replace("__", ":", 1)
|
key = key.replace("__", ":", 1)
|
||||||
@@ -181,7 +223,6 @@ class SenpyModel(SenpyMixin, dict):
|
|||||||
def __setitem__(self, key, value):
|
def __setitem__(self, key, value):
|
||||||
dict.__setitem__(self, key, value)
|
dict.__setitem__(self, key, value)
|
||||||
|
|
||||||
|
|
||||||
def __delitem__(self, key):
|
def __delitem__(self, key):
|
||||||
dict.__delitem__(self, key)
|
dict.__delitem__(self, key)
|
||||||
|
|
||||||
@@ -197,52 +238,104 @@ class SenpyModel(SenpyMixin, dict):
|
|||||||
def __delattr__(self, key):
|
def __delattr__(self, key):
|
||||||
self.__delitem__(self._get_key(key))
|
self.__delitem__(self._get_key(key))
|
||||||
|
|
||||||
|
|
||||||
def _plain_dict(self):
|
def _plain_dict(self):
|
||||||
d = {k: v for (k, v) in self.items() if k[0] != "_"}
|
d = {k: v for (k, v) in self.items() if k[0] != "_"}
|
||||||
|
if 'id' in d:
|
||||||
d["@id"] = d.pop('id')
|
d["@id"] = d.pop('id')
|
||||||
return d
|
return d
|
||||||
|
|
||||||
class Response(SenpyModel):
|
|
||||||
schema = read_schema('response.json')
|
|
||||||
|
|
||||||
class Results(SenpyModel):
|
_subtypes = {}
|
||||||
schema = read_schema('results.json')
|
|
||||||
|
|
||||||
class Entry(SenpyModel):
|
|
||||||
schema = read_schema('entry.json')
|
|
||||||
|
|
||||||
class Sentiment(SenpyModel):
|
def register(rsubclass, rtype=None):
|
||||||
schema = read_schema('sentiment.json')
|
_subtypes[rtype or rsubclass.__name__] = rsubclass
|
||||||
|
|
||||||
class Analysis(SenpyModel):
|
|
||||||
schema = read_schema('analysis.json')
|
|
||||||
|
|
||||||
class EmotionSet(SenpyModel):
|
def from_dict(indict):
|
||||||
schema = read_schema('emotionSet.json')
|
target = indict.get('@type', None)
|
||||||
|
if target and target in _subtypes:
|
||||||
|
cls = _subtypes[target]
|
||||||
|
else:
|
||||||
|
cls = BaseModel
|
||||||
|
return cls(**indict)
|
||||||
|
|
||||||
class Emotion(SenpyModel):
|
|
||||||
schema = read_schema('emotion.json')
|
|
||||||
|
|
||||||
class Suggestion(SenpyModel):
|
def from_json(injson):
|
||||||
schema = read_schema('suggestion.json')
|
indict = json.loads(injson)
|
||||||
|
return from_dict(indict)
|
||||||
|
|
||||||
class PluginModel(SenpyModel):
|
|
||||||
schema = read_schema('plugin.json')
|
|
||||||
|
|
||||||
class Plugins(SenpyModel):
|
def from_schema(name, schema_file=None, base_classes=None):
|
||||||
schema = read_schema('plugins.json')
|
base_classes = base_classes or []
|
||||||
|
base_classes.append(BaseModel)
|
||||||
|
schema_file = schema_file or '{}.json'.format(name)
|
||||||
|
class_name = '{}{}'.format(name[0].upper(), name[1:])
|
||||||
|
newclass = type(class_name, tuple(base_classes), {})
|
||||||
|
setattr(newclass, '@type', name)
|
||||||
|
setattr(newclass, 'schema', read_schema(schema_file))
|
||||||
|
setattr(newclass, 'class_name', class_name)
|
||||||
|
register(newclass, name)
|
||||||
|
return newclass
|
||||||
|
|
||||||
|
|
||||||
|
def _add_from_schema(*args, **kwargs):
|
||||||
|
generatedClass = from_schema(*args, **kwargs)
|
||||||
|
globals()[generatedClass.__name__] = generatedClass
|
||||||
|
del generatedClass
|
||||||
|
|
||||||
|
|
||||||
|
for i in [
|
||||||
|
'analysis',
|
||||||
|
'emotion',
|
||||||
|
'emotionConversion',
|
||||||
|
'emotionConversionPlugin',
|
||||||
|
'emotionAnalysis',
|
||||||
|
'emotionModel',
|
||||||
|
'emotionPlugin',
|
||||||
|
'emotionSet',
|
||||||
|
'entry',
|
||||||
|
'plugin',
|
||||||
|
'plugins',
|
||||||
|
'response',
|
||||||
|
'results',
|
||||||
|
'sentiment',
|
||||||
|
'sentimentPlugin',
|
||||||
|
'suggestion',
|
||||||
|
]:
|
||||||
|
_add_from_schema(i)
|
||||||
|
|
||||||
|
_ErrorModel = from_schema('error')
|
||||||
|
|
||||||
|
|
||||||
class Error(SenpyMixin, BaseException):
|
class Error(SenpyMixin, BaseException):
|
||||||
|
def __init__(self, message, *args, **kwargs):
|
||||||
def __init__(self, message, status=500, params=None, errors=None, *args, **kwargs):
|
super(Error, self).__init__(self, message, message)
|
||||||
|
self._error = _ErrorModel(message=message, *args, **kwargs)
|
||||||
self.message = message
|
self.message = message
|
||||||
self.status = status
|
|
||||||
self.params = params or {}
|
|
||||||
self.errors = errors or ""
|
|
||||||
|
|
||||||
def _plain_dict(self):
|
def __getitem__(self, key):
|
||||||
return self.__dict__
|
return self._error[key]
|
||||||
|
|
||||||
def __str__(self):
|
def __setitem__(self, key, value):
|
||||||
return str(self.jsonld())
|
self._error[key] = value
|
||||||
|
|
||||||
|
def __delitem__(self, key):
|
||||||
|
del self._error[key]
|
||||||
|
|
||||||
|
def __getattr__(self, key):
|
||||||
|
if key != '_error' and hasattr(self._error, key):
|
||||||
|
return getattr(self._error, key)
|
||||||
|
raise AttributeError(key)
|
||||||
|
|
||||||
|
def __setattr__(self, key, value):
|
||||||
|
if key != '_error':
|
||||||
|
return setattr(self._error, key, value)
|
||||||
|
else:
|
||||||
|
super(Error, self).__setattr__(key, value)
|
||||||
|
|
||||||
|
def __delattr__(self, key):
|
||||||
|
delattr(self._error, key)
|
||||||
|
|
||||||
|
|
||||||
|
register(Error, 'error')
|
||||||
|
@@ -5,28 +5,47 @@ import inspect
|
|||||||
import os.path
|
import os.path
|
||||||
import pickle
|
import pickle
|
||||||
import logging
|
import logging
|
||||||
from .models import Response, PluginModel, Error
|
import tempfile
|
||||||
|
import copy
|
||||||
|
from . import models
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class SenpyPlugin(PluginModel):
|
|
||||||
|
|
||||||
|
class SenpyPlugin(models.Plugin):
|
||||||
def __init__(self, info=None):
|
def __init__(self, info=None):
|
||||||
|
"""
|
||||||
|
Provides a canonical name for plugins and serves as base for other
|
||||||
|
kinds of plugins.
|
||||||
|
"""
|
||||||
if not info:
|
if not info:
|
||||||
raise Error(message=("You need to provide configuration"
|
raise models.Error(message=("You need to provide configuration"
|
||||||
"information for the plugin."))
|
"information for the plugin."))
|
||||||
logger.debug("Initialising {}".format(info))
|
logger.debug("Initialising {}".format(info))
|
||||||
super(SenpyPlugin, self).__init__(info)
|
id = 'plugins/{}_{}'.format(info['name'], info['version'])
|
||||||
self.id = '{}_{}'.format(self.name, self.version)
|
super(SenpyPlugin, self).__init__(id=id, **info)
|
||||||
self._info = info
|
|
||||||
self.is_activated = False
|
self.is_activated = False
|
||||||
|
|
||||||
def get_folder(self):
|
def get_folder(self):
|
||||||
return os.path.dirname(inspect.getfile(self.__class__))
|
return os.path.dirname(inspect.getfile(self.__class__))
|
||||||
|
|
||||||
def analyse(self, *args, **kwargs):
|
def analyse(self, *args, **kwargs):
|
||||||
logger.debug("Analysing with: {} {}".format(self.name, self.version))
|
raise NotImplemented(
|
||||||
pass
|
'Your method should implement either analyse or analyse_entry')
|
||||||
|
|
||||||
|
def analyse_entry(self, entry, parameters):
|
||||||
|
""" An implemented plugin should override this method.
|
||||||
|
This base method is here to adapt old style plugins which only
|
||||||
|
implement the *analyse* function.
|
||||||
|
Note that this method may yield an annotated entry or a list of
|
||||||
|
entries (e.g. in a tokenizer)
|
||||||
|
"""
|
||||||
|
text = entry['text']
|
||||||
|
params = copy.copy(parameters)
|
||||||
|
params['input'] = text
|
||||||
|
results = self.analyse(**params)
|
||||||
|
for i in results.entries:
|
||||||
|
yield i
|
||||||
|
|
||||||
def activate(self):
|
def activate(self):
|
||||||
pass
|
pass
|
||||||
@@ -34,30 +53,27 @@ class SenpyPlugin(PluginModel):
|
|||||||
def deactivate(self):
|
def deactivate(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
''' Destructor, to make sure all the resources are freed '''
|
|
||||||
self.deactivate()
|
|
||||||
|
|
||||||
class SentimentPlugin(SenpyPlugin):
|
|
||||||
|
|
||||||
|
class SentimentPlugin(models.SentimentPlugin, SenpyPlugin):
|
||||||
def __init__(self, info, *args, **kwargs):
|
def __init__(self, info, *args, **kwargs):
|
||||||
super(SentimentPlugin, self).__init__(info, *args, **kwargs)
|
super(SentimentPlugin, self).__init__(info, *args, **kwargs)
|
||||||
self.minPolarityValue = float(info.get("minPolarityValue", 0))
|
self.minPolarityValue = float(info.get("minPolarityValue", 0))
|
||||||
self.maxPolarityValue = float(info.get("maxPolarityValue", 1))
|
self.maxPolarityValue = float(info.get("maxPolarityValue", 1))
|
||||||
self["@type"] = "marl:SentimentAnalysis"
|
|
||||||
|
|
||||||
|
|
||||||
class EmotionPlugin(SenpyPlugin):
|
class EmotionPlugin(models.EmotionPlugin, SenpyPlugin):
|
||||||
|
|
||||||
def __init__(self, info, *args, **kwargs):
|
def __init__(self, info, *args, **kwargs):
|
||||||
resp = super(EmotionPlugin, self).__init__(info, *args, **kwargs)
|
super(EmotionPlugin, self).__init__(info, *args, **kwargs)
|
||||||
self.minEmotionValue = float(info.get("minEmotionValue", 0))
|
self.minEmotionValue = float(info.get("minEmotionValue", -1))
|
||||||
self.maxEmotionValue = float(info.get("maxEmotionValue", 0))
|
self.maxEmotionValue = float(info.get("maxEmotionValue", 1))
|
||||||
self["@type"] = "onyx:EmotionAnalysis"
|
|
||||||
|
|
||||||
|
class EmotionConversionPlugin(models.EmotionConversionPlugin, SenpyPlugin):
|
||||||
|
def __init__(self, info, *args, **kwargs):
|
||||||
|
super(EmotionConversionPlugin, self).__init__(info, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class ShelfMixin(object):
|
class ShelfMixin(object):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sh(self):
|
def sh(self):
|
||||||
if not hasattr(self, '_sh') or self._sh is None:
|
if not hasattr(self, '_sh') or self._sh is None:
|
||||||
@@ -73,22 +89,15 @@ class ShelfMixin(object):
|
|||||||
del self.__dict__['_sh']
|
del self.__dict__['_sh']
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
self.save()
|
|
||||||
super(ShelfMixin, self).__del__()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def shelf_file(self):
|
def shelf_file(self):
|
||||||
if not hasattr(self, '_shelf_file') or not self._shelf_file:
|
if 'shelf_file' not in self or not self['shelf_file']:
|
||||||
if hasattr(self, '_info') and 'shelf_file' in self._info:
|
self.shelf_file = os.path.join(tempfile.gettempdir(),
|
||||||
self.__dict__['_shelf_file'] = self._info['shelf_file']
|
self.name + '.p')
|
||||||
else:
|
return self['shelf_file']
|
||||||
self._shelf_file = os.path.join(self.get_folder(), self.name + '.p')
|
|
||||||
return self._shelf_file
|
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
logger.debug('closing pickle')
|
logger.debug('saving pickle')
|
||||||
if hasattr(self, '_sh') and self._sh is not None:
|
if hasattr(self, '_sh') and self._sh is not None:
|
||||||
with open(self.shelf_file, 'wb') as f:
|
with open(self.shelf_file, 'wb') as f:
|
||||||
pickle.dump(self._sh, f)
|
pickle.dump(self._sh, f)
|
||||||
del(self.__dict__['_sh'])
|
|
||||||
|
56
senpy/plugins/conversion/emotion/ekman2vad.py
Normal file
56
senpy/plugins/conversion/emotion/ekman2vad.py
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
from senpy.plugins import EmotionConversionPlugin
|
||||||
|
from senpy.models import EmotionSet, Emotion, Error
|
||||||
|
|
||||||
|
import logging
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
import math
|
||||||
|
|
||||||
|
|
||||||
|
class WNA2VAD(EmotionConversionPlugin):
|
||||||
|
|
||||||
|
def _ekman_to_vad(self, ekmanSet):
|
||||||
|
potency = 0
|
||||||
|
arousal = 0
|
||||||
|
dominance = 0
|
||||||
|
for e in ekmanSet.onyx__hasEmotion:
|
||||||
|
category = e.onyx__hasEmotionCategory
|
||||||
|
centroid = self.centroids[category]
|
||||||
|
potency += centroid['V']
|
||||||
|
arousal += centroid['A']
|
||||||
|
dominance += centroid['D']
|
||||||
|
e = Emotion({'emoml:potency': potency,
|
||||||
|
'emoml:arousal': arousal,
|
||||||
|
'emoml:dominance': dominance})
|
||||||
|
return e
|
||||||
|
|
||||||
|
def _vad_to_ekman(self, VADEmotion):
|
||||||
|
V = VADEmotion['emoml:valence']
|
||||||
|
A = VADEmotion['emoml:potency']
|
||||||
|
D = VADEmotion['emoml:dominance']
|
||||||
|
emotion = ''
|
||||||
|
value = 10000000000000000000000.0
|
||||||
|
for state in self.centroids:
|
||||||
|
valence = V - self.centroids[state]['V']
|
||||||
|
arousal = A - self.centroids[state]['A']
|
||||||
|
dominance = D - self.centroids[state]['D']
|
||||||
|
new_value = math.sqrt((valence**2) +
|
||||||
|
(arousal**2) +
|
||||||
|
(dominance**2))
|
||||||
|
if new_value < value:
|
||||||
|
value = new_value
|
||||||
|
emotion = state
|
||||||
|
result = Emotion(onyx__hasEmotionCategory=emotion)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def convert(self, emotionSet, fromModel, toModel, params):
|
||||||
|
logger.debug('{}\n{}\n{}\n{}'.format(emotionSet, fromModel, toModel, params))
|
||||||
|
e = EmotionSet()
|
||||||
|
if fromModel == 'emoml:big6':
|
||||||
|
e.onyx__hasEmotion.append(self._ekman_to_vad(emotionSet))
|
||||||
|
elif fromModel == 'emoml:fsre-dimensions':
|
||||||
|
for i in emotionSet.onyx__hasEmotion:
|
||||||
|
e.onyx__hasEmotion.append(self._vad_to_ekman(e))
|
||||||
|
else:
|
||||||
|
raise Error('EMOTION MODEL NOT KNOWN')
|
||||||
|
yield e
|
35
senpy/plugins/conversion/emotion/ekman2vad.senpy
Normal file
35
senpy/plugins/conversion/emotion/ekman2vad.senpy
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
name: Ekman2VAD
|
||||||
|
module: ekman2vad
|
||||||
|
description: Plugin to convert from Ekman to VAD
|
||||||
|
version: 0.1
|
||||||
|
onyx:doesConversion:
|
||||||
|
- onyx:conversionFrom: emoml:big6
|
||||||
|
onyx:conversionTo: emoml:fsre-dimensions
|
||||||
|
- onyx:conversionFrom: emoml:fsre-dimensions
|
||||||
|
onyx:conversionTo: wna:WNAModel
|
||||||
|
centroids:
|
||||||
|
emoml:big6anger:
|
||||||
|
A: 6.95
|
||||||
|
D: 5.1
|
||||||
|
V: 2.7
|
||||||
|
emoml:big6disgust:
|
||||||
|
A: 5.3
|
||||||
|
D: 8.05
|
||||||
|
V: 2.7
|
||||||
|
emoml:big6fear:
|
||||||
|
A: 6.5
|
||||||
|
D: 3.6
|
||||||
|
V: 3.2
|
||||||
|
emoml:big6happiness:
|
||||||
|
A: 7.22
|
||||||
|
D: 6.28
|
||||||
|
V: 8.6
|
||||||
|
emoml:big6sadness:
|
||||||
|
A: 5.21
|
||||||
|
D: 2.82
|
||||||
|
V: 2.21
|
||||||
|
aliases:
|
||||||
|
A: emoml:arousal
|
||||||
|
V: emoml:potency
|
||||||
|
D: emoml:dominance
|
18
senpy/plugins/example/emoRand/emoRand.py
Normal file
18
senpy/plugins/example/emoRand/emoRand.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import random
|
||||||
|
|
||||||
|
from senpy.plugins import EmotionPlugin
|
||||||
|
from senpy.models import EmotionSet, Emotion
|
||||||
|
|
||||||
|
|
||||||
|
class RmoRandPlugin(EmotionPlugin):
|
||||||
|
def analyse_entry(self, entry, params):
|
||||||
|
category = "emoml:big6happiness"
|
||||||
|
number = max(-1, min(1, random.gauss(0, 0.5)))
|
||||||
|
if number > 0:
|
||||||
|
category = "emoml:big6anger"
|
||||||
|
emotionSet = EmotionSet()
|
||||||
|
emotion = Emotion({"onyx:hasEmotionCategory": category})
|
||||||
|
emotionSet.onyx__hasEmotion.append(emotion)
|
||||||
|
emotionSet.prov__wasGeneratedBy = self.id
|
||||||
|
entry.emotions.append(emotionSet)
|
||||||
|
yield entry
|
9
senpy/plugins/example/emoRand/emoRand.senpy
Normal file
9
senpy/plugins/example/emoRand/emoRand.senpy
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
name: emoRand
|
||||||
|
module: emoRand
|
||||||
|
description: A sample plugin that returns a random emotion annotation
|
||||||
|
author: "@balkian"
|
||||||
|
version: '0.1'
|
||||||
|
url: "https://github.com/gsi-upm/senpy-plugins-community"
|
||||||
|
requirements: {}
|
||||||
|
onyx:usesEmotionModel: "emoml:big6"
|
24
senpy/plugins/example/rand/rand.py
Normal file
24
senpy/plugins/example/rand/rand.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import random
|
||||||
|
|
||||||
|
from senpy.plugins import SentimentPlugin
|
||||||
|
from senpy.models import Sentiment
|
||||||
|
|
||||||
|
|
||||||
|
class RandPlugin(SentimentPlugin):
|
||||||
|
def analyse_entry(self, entry, params):
|
||||||
|
lang = params.get("language", "auto")
|
||||||
|
|
||||||
|
polarity_value = max(-1, min(1, random.gauss(0.2, 0.2)))
|
||||||
|
polarity = "marl:Neutral"
|
||||||
|
if polarity_value > 0:
|
||||||
|
polarity = "marl:Positive"
|
||||||
|
elif polarity_value < 0:
|
||||||
|
polarity = "marl:Negative"
|
||||||
|
sentiment = Sentiment({
|
||||||
|
"marl:hasPolarity": polarity,
|
||||||
|
"marl:polarityValue": polarity_value
|
||||||
|
})
|
||||||
|
sentiment["prov:wasGeneratedBy"] = self.id
|
||||||
|
entry.sentiments.append(sentiment)
|
||||||
|
entry.language = lang
|
||||||
|
yield entry
|
10
senpy/plugins/example/rand/rand.senpy
Normal file
10
senpy/plugins/example/rand/rand.senpy
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
name: rand
|
||||||
|
module: rand
|
||||||
|
description: A sample plugin that returns a random sentiment annotation
|
||||||
|
author: "@balkian"
|
||||||
|
version: '0.1'
|
||||||
|
url: "https://github.com/gsi-upm/senpy-plugins-community"
|
||||||
|
requirements: {}
|
||||||
|
marl:maxPolarityValue: '1'
|
||||||
|
marl:minPolarityValue: "-1"
|
@@ -1,41 +0,0 @@
|
|||||||
import json
|
|
||||||
import random
|
|
||||||
|
|
||||||
from senpy.plugins import SentimentPlugin
|
|
||||||
from senpy.models import Results, Sentiment, Entry
|
|
||||||
|
|
||||||
|
|
||||||
class Sentiment140Plugin(SentimentPlugin):
|
|
||||||
def analyse(self, **params):
|
|
||||||
lang = params.get("language", "auto")
|
|
||||||
|
|
||||||
response = Results()
|
|
||||||
polarity_value = max(-1, min(1, random.gauss(0.2, 0.2)))
|
|
||||||
polarity = "marl:Neutral"
|
|
||||||
if polarity_value > 0:
|
|
||||||
polarity = "marl:Positive"
|
|
||||||
elif polarity_value < 0:
|
|
||||||
polarity = "marl:Negative"
|
|
||||||
entry = Entry({"id":":Entry0",
|
|
||||||
"nif:isString": params["input"]})
|
|
||||||
sentiment = Sentiment({"id": ":Sentiment0",
|
|
||||||
"marl:hasPolarity": polarity,
|
|
||||||
"marl:polarityValue": polarity_value})
|
|
||||||
sentiment["prov:wasGeneratedBy"] = self.id
|
|
||||||
entry.sentiments = []
|
|
||||||
entry.sentiments.append(sentiment)
|
|
||||||
entry.language = lang
|
|
||||||
response.entries.append(entry)
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "rand",
|
|
||||||
"module": "rand",
|
|
||||||
"description": "What my plugin broadly does",
|
|
||||||
"author": "@balkian",
|
|
||||||
"version": "0.1",
|
|
||||||
"extra_params": {
|
|
||||||
"language": {
|
|
||||||
"@id": "lang_rand",
|
|
||||||
"aliases": ["language", "l"],
|
|
||||||
"required": false,
|
|
||||||
"options": ["es", "en", "auto"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"requirements": {},
|
|
||||||
"marl:maxPolarityValue": "1",
|
|
||||||
"marl:minPolarityValue": "-1"
|
|
||||||
}
|
|
36
senpy/plugins/sentiment/sentiment140/sentiment140.py
Normal file
36
senpy/plugins/sentiment/sentiment140/sentiment140.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import requests
|
||||||
|
import json
|
||||||
|
|
||||||
|
from senpy.plugins import SentimentPlugin
|
||||||
|
from senpy.models import Sentiment
|
||||||
|
|
||||||
|
|
||||||
|
class Sentiment140Plugin(SentimentPlugin):
|
||||||
|
def analyse_entry(self, entry, params):
|
||||||
|
lang = params.get("language", "auto")
|
||||||
|
res = requests.post("http://www.sentiment140.com/api/bulkClassifyJson",
|
||||||
|
json.dumps({
|
||||||
|
"language": lang,
|
||||||
|
"data": [{
|
||||||
|
"text": entry.text
|
||||||
|
}]
|
||||||
|
}))
|
||||||
|
p = params.get("prefix", None)
|
||||||
|
polarity_value = self.maxPolarityValue * int(
|
||||||
|
res.json()["data"][0]["polarity"]) * 0.25
|
||||||
|
polarity = "marl:Neutral"
|
||||||
|
neutral_value = self.maxPolarityValue / 2.0
|
||||||
|
if polarity_value > neutral_value:
|
||||||
|
polarity = "marl:Positive"
|
||||||
|
elif polarity_value < neutral_value:
|
||||||
|
polarity = "marl:Negative"
|
||||||
|
|
||||||
|
sentiment = Sentiment(
|
||||||
|
prefix=p,
|
||||||
|
marl__hasPolarity=polarity,
|
||||||
|
marl__polarityValue=polarity_value)
|
||||||
|
sentiment.prov__wasGeneratedBy = self.id
|
||||||
|
entry.sentiments = []
|
||||||
|
entry.sentiments.append(sentiment)
|
||||||
|
entry.language = lang
|
||||||
|
yield entry
|
21
senpy/plugins/sentiment/sentiment140/sentiment140.senpy
Normal file
21
senpy/plugins/sentiment/sentiment140/sentiment140.senpy
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
name: sentiment140
|
||||||
|
module: sentiment140
|
||||||
|
description: "Connects to the sentiment140 free API: http://sentiment140.com"
|
||||||
|
author: "@balkian"
|
||||||
|
version: '0.2'
|
||||||
|
url: "https://github.com/gsi-upm/senpy-plugins-community"
|
||||||
|
extra_params:
|
||||||
|
language:
|
||||||
|
"@id": lang_sentiment140
|
||||||
|
aliases:
|
||||||
|
- language
|
||||||
|
- l
|
||||||
|
required: false
|
||||||
|
options:
|
||||||
|
- es
|
||||||
|
- en
|
||||||
|
- auto
|
||||||
|
requirements: {}
|
||||||
|
maxPolarityValue: 1
|
||||||
|
minPolarityValue: 0
|
@@ -1,40 +0,0 @@
|
|||||||
import requests
|
|
||||||
import json
|
|
||||||
|
|
||||||
from senpy.plugins import SentimentPlugin
|
|
||||||
from senpy.models import Results, Sentiment, Entry
|
|
||||||
|
|
||||||
|
|
||||||
class Sentiment140Plugin(SentimentPlugin):
|
|
||||||
def analyse(self, **params):
|
|
||||||
lang = params.get("language", "auto")
|
|
||||||
res = requests.post("http://www.sentiment140.com/api/bulkClassifyJson",
|
|
||||||
json.dumps({"language": lang,
|
|
||||||
"data": [{"text": params["input"]}]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
p = params.get("prefix", None)
|
|
||||||
response = Results(prefix=p)
|
|
||||||
polarity_value = self.maxPolarityValue*int(res.json()["data"][0]
|
|
||||||
["polarity"]) * 0.25
|
|
||||||
polarity = "marl:Neutral"
|
|
||||||
neutral_value = self.maxPolarityValue / 2.0
|
|
||||||
if polarity_value > neutral_value:
|
|
||||||
polarity = "marl:Positive"
|
|
||||||
elif polarity_value < neutral_value:
|
|
||||||
polarity = "marl:Negative"
|
|
||||||
|
|
||||||
entry = Entry(id="Entry0",
|
|
||||||
nif__isString=params["input"])
|
|
||||||
sentiment = Sentiment(id="Sentiment0",
|
|
||||||
prefix=p,
|
|
||||||
marl__hasPolarity=polarity,
|
|
||||||
marl__polarityValue=polarity_value)
|
|
||||||
sentiment.prov__wasGeneratedBy = self.id
|
|
||||||
entry.sentiments = []
|
|
||||||
entry.sentiments.append(sentiment)
|
|
||||||
entry.language = lang
|
|
||||||
response.entries.append(entry)
|
|
||||||
return response
|
|
@@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "sentiment140",
|
|
||||||
"module": "sentiment140",
|
|
||||||
"description": "What my plugin broadly does",
|
|
||||||
"author": "@balkian",
|
|
||||||
"version": "0.1",
|
|
||||||
"extra_params": {
|
|
||||||
"language": {
|
|
||||||
"@id": "lang_sentiment140",
|
|
||||||
"aliases": ["language", "l"],
|
|
||||||
"required": false,
|
|
||||||
"options": ["es", "en", "auto"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"requirements": {},
|
|
||||||
"maxPolarityValue": "1",
|
|
||||||
"minPolarityValue": "0"
|
|
||||||
}
|
|
@@ -1,4 +1,15 @@
|
|||||||
{
|
{
|
||||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
"$ref": "definitions.json#/Analysis"
|
"description": "Senpy analysis",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"@id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"@type": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Type of the analysis. e.g. marl:SentimentAnalysis"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["@id", "@type"]
|
||||||
}
|
}
|
||||||
|
15
senpy/schemas/atom.json
Normal file
15
senpy/schemas/atom.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"description": "Base schema for all Senpy objects",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"@id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"@type": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Type of the atom. e.g., 'onyx:EmotionAnalysis', 'nif:Entry'"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["@id", "@type"]
|
||||||
|
}
|
5
senpy/schemas/context.json
Normal file
5
senpy/schemas/context.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"description": "JSON-LD Context",
|
||||||
|
"type": ["array", "string", "object"]
|
||||||
|
}
|
@@ -6,8 +6,9 @@
|
|||||||
"prov": "http://www.w3.org/ns/prov#",
|
"prov": "http://www.w3.org/ns/prov#",
|
||||||
"nif": "http://persistence.uni-leipzig.org/nlp2rdf/ontologies/nif-core#",
|
"nif": "http://persistence.uni-leipzig.org/nlp2rdf/ontologies/nif-core#",
|
||||||
"marl": "http://www.gsi.dit.upm.es/ontologies/marl/ns#",
|
"marl": "http://www.gsi.dit.upm.es/ontologies/marl/ns#",
|
||||||
"onyx": "http://www.gsi.dit.upm.es/ontologies/onyx#",
|
"onyx": "http://www.gsi.dit.upm.es/ontologies/onyx/ns#",
|
||||||
"wnaffect": "http://www.gsi.dit.upm.es/ontologies/wnaffect#",
|
"wna": "http://www.gsi.dit.upm.es/ontologies/wnaffect/ns#",
|
||||||
|
"emoml": "http://www.gsi.dit.upm.es/ontologies/onyx/vocabularies/emotionml/ns#",
|
||||||
"xsd": "http://www.w3.org/2001/XMLSchema#",
|
"xsd": "http://www.w3.org/2001/XMLSchema#",
|
||||||
"topics": {
|
"topics": {
|
||||||
"@id": "dc:subject"
|
"@id": "dc:subject"
|
||||||
@@ -16,20 +17,40 @@
|
|||||||
"@id": "me:hasEntities"
|
"@id": "me:hasEntities"
|
||||||
},
|
},
|
||||||
"suggestions": {
|
"suggestions": {
|
||||||
"@id": "me:hasSuggestions"
|
"@id": "me:hasSuggestions",
|
||||||
|
"@container": "@set"
|
||||||
},
|
},
|
||||||
"emotions": {
|
"emotions": {
|
||||||
"@id": "onyx:hasEmotionSet"
|
"@id": "onyx:hasEmotionSet",
|
||||||
|
"@container": "@set"
|
||||||
},
|
},
|
||||||
"sentiments": {
|
"sentiments": {
|
||||||
"@id": "marl:hasOpinion"
|
"@id": "marl:hasOpinion",
|
||||||
|
"@container": "@set"
|
||||||
},
|
},
|
||||||
"entries": {
|
"entries": {
|
||||||
"@id": "prov:used"
|
"@id": "prov:used",
|
||||||
|
"@container": "@set"
|
||||||
},
|
},
|
||||||
"analysis": {
|
"analysis": {
|
||||||
"@id": "prov:wasGeneratedBy"
|
"@id": "AnalysisInvolved",
|
||||||
|
"@type": "@id",
|
||||||
|
"@container": "@set"
|
||||||
|
},
|
||||||
|
"prov:wasGeneratedBy": {
|
||||||
|
"@type": "@id"
|
||||||
|
},
|
||||||
|
"onyx:usesEmotionModel": {
|
||||||
|
"@type": "@id"
|
||||||
|
},
|
||||||
|
"onyx:hasEmotionCategory": {
|
||||||
|
"@type": "@id"
|
||||||
|
},
|
||||||
|
"onyx:conversionFrom": {
|
||||||
|
"@type": "@id"
|
||||||
|
},
|
||||||
|
"onyx:conversionTo": {
|
||||||
|
"@type": "@id"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,169 +1,45 @@
|
|||||||
{
|
{
|
||||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
"Results": {
|
"Results": {
|
||||||
"title": "Results",
|
"$ref": "results.json"
|
||||||
"description": "The results of an analysis",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"@context": {
|
|
||||||
"$ref": "#/Context"
|
|
||||||
},
|
|
||||||
"@id": {
|
|
||||||
"description": "ID of the analysis",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"analysis": {
|
|
||||||
"type": "array",
|
|
||||||
"default": [],
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/Analysis"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"entries": {
|
|
||||||
"type": "array",
|
|
||||||
"default": [],
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/Entry"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
"required": ["@id", "analysis", "entries"]
|
|
||||||
},
|
},
|
||||||
"Context": {
|
"Context": {
|
||||||
"description": "JSON-LD Context",
|
"$ref": "context.json"
|
||||||
"type": ["array", "string", "object"]
|
|
||||||
},
|
},
|
||||||
"Analysis": {
|
"Analysis": {
|
||||||
"description": "Senpy analysis",
|
"$ref": "analysis.json"
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"@id": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"@type": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Type of the analysis. e.g. marl:SentimentAnalysis"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["@id", "@type"]
|
|
||||||
},
|
},
|
||||||
"Entry": {
|
"Entry": {
|
||||||
"properties": {
|
"$ref": "entry.json"
|
||||||
"@id": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"@type": {
|
|
||||||
"enum": [["nif:RFC5147String", "nif:Context"]]
|
|
||||||
},
|
|
||||||
"nif:isString": {
|
|
||||||
"description": "String contained in this Context",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"sentiments": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {"$ref": "#/Sentiment" }
|
|
||||||
},
|
|
||||||
"emotions": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {"$ref": "#/EmotionSet" }
|
|
||||||
},
|
|
||||||
"entities": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {"$ref": "#/Entity" }
|
|
||||||
},
|
|
||||||
"topics": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {"$ref": "#/Topic" }
|
|
||||||
},
|
|
||||||
"suggestions": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {"$ref": "#/Suggestion" }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["@id", "nif:isString"]
|
|
||||||
},
|
},
|
||||||
"Sentiment": {
|
"Sentiment": {
|
||||||
"properties": {
|
"$ref": "sentiment.json"
|
||||||
"@id": {"type": "string"},
|
|
||||||
"nif:beginIndex": {"type": "integer"},
|
|
||||||
"nif:endIndex": {"type": "integer"},
|
|
||||||
"nif:anchorOf": {
|
|
||||||
"description": "Piece of context that contains the Sentiment",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"marl:hasPolarity": {
|
|
||||||
"enum": ["marl:Positive", "marl:Negative", "marl:Neutral"]
|
|
||||||
},
|
|
||||||
"marl:polarityValue": {
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"prov:wasGeneratedBy": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "The ID of the analysis that generated this Sentiment. The full object should be included in the \"analysis\" property of the root object"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["@id", "prov:wasGeneratedBy"]
|
|
||||||
},
|
},
|
||||||
"EmotionSet": {
|
"EmotionSet": {
|
||||||
"properties": {
|
"$ref": "emotionSet.json"
|
||||||
"@id": {"type": "string"},
|
|
||||||
"nif:beginIndex": {"type": "integer"},
|
|
||||||
"nif:endIndex": {"type": "integer"},
|
|
||||||
"nif:anchorOf": {
|
|
||||||
"description": "Piece of context that contains the Sentiment",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"onyx:hasEmotion": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/Emotion"
|
|
||||||
},
|
|
||||||
"default": []
|
|
||||||
},
|
|
||||||
"prov:wasGeneratedBy": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "The ID of the analysis that generated this Emotion. The full object should be included in the \"analysis\" property of the root object"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["@id", "prov:wasGeneratedBy", "onyx:hasEmotion"]
|
|
||||||
},
|
},
|
||||||
"Emotion": {
|
"Emotion": {
|
||||||
"type": "object"
|
"$ref": "emotion.json"
|
||||||
|
},
|
||||||
|
"EmotionModel": {
|
||||||
|
"$ref": "emotionModel.json"
|
||||||
},
|
},
|
||||||
"Entity": {
|
"Entity": {
|
||||||
"type": "object"
|
"$ref": "entity.json"
|
||||||
},
|
},
|
||||||
"Topic": {
|
"Topic": {
|
||||||
"type": "object"
|
"$ref": "topic.json"
|
||||||
},
|
},
|
||||||
"Suggestion": {
|
"Suggestion": {
|
||||||
"type": "object"
|
"$ref": "suggestion.json"
|
||||||
},
|
},
|
||||||
"Plugins": {
|
"Plugins": {
|
||||||
"properties": {
|
"$ref": "plugin.json"
|
||||||
"plugins": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/Plugin"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"Plugin": {
|
"Plugin": {
|
||||||
"type": "object",
|
"$ref": "plugin.json"
|
||||||
"required": ["@id", "extra_params"],
|
|
||||||
"properties": {
|
|
||||||
"@id": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"extra_params": {
|
|
||||||
"type": "object",
|
|
||||||
"default": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"Response": {
|
"Response": {
|
||||||
"type": "object"
|
"$ref": "response.json"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
9
senpy/schemas/dimensions.json
Normal file
9
senpy/schemas/dimensions.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"properties": {
|
||||||
|
"name": {"type": "string"},
|
||||||
|
"maxValue": {"type": "number"},
|
||||||
|
"minValue": {"type": "number"}
|
||||||
|
},
|
||||||
|
"required": ["name", "maxValue", "minValue"]
|
||||||
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
"$ref": "definitions.json#/Emotion"
|
"type": "object"
|
||||||
}
|
}
|
||||||
|
19
senpy/schemas/emotionAnalysis.json
Normal file
19
senpy/schemas/emotionAnalysis.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"description": "Senpy Emotion analysis",
|
||||||
|
"type": "object",
|
||||||
|
"allOf": [
|
||||||
|
{"$ref": "analysis.json"},
|
||||||
|
{"properties":
|
||||||
|
{
|
||||||
|
"onyx:usesEmotionModel": {
|
||||||
|
"anyOf": [
|
||||||
|
{"type": "string"},
|
||||||
|
{"$ref": "emotionModel.json"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["onyx:hasEmotionModel",
|
||||||
|
"@type"]
|
||||||
|
}]
|
||||||
|
}
|
12
senpy/schemas/emotionConversion.json
Normal file
12
senpy/schemas/emotionConversion.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"properties": {
|
||||||
|
"onyx:conversionFrom": {
|
||||||
|
"$ref": "emotionModel.json"
|
||||||
|
},
|
||||||
|
"onyx:conversionTo": {
|
||||||
|
"$ref": "emotionModel.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["onyx:conversionFrom", "onyx:conversionTo"]
|
||||||
|
}
|
19
senpy/schemas/emotionConversionPlugin.json
Normal file
19
senpy/schemas/emotionConversionPlugin.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"type": "object",
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "plugin.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"properties": {
|
||||||
|
"onyx:doesConversion": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "emotionConversion.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
27
senpy/schemas/emotionModel.json
Normal file
27
senpy/schemas/emotionModel.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"properties": {
|
||||||
|
"@id": {"type": "string"},
|
||||||
|
"nif:beginIndex": {"type": "integer"},
|
||||||
|
"nif:endIndex": {"type": "integer"},
|
||||||
|
"nif:anchorOf": {
|
||||||
|
"description": "Piece of context that contains the Sentiment",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"onyx:hasDimension": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "dimensions.json"
|
||||||
|
},
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
"onyx:hasEmotionCategory": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "emotion.json"
|
||||||
|
},
|
||||||
|
"default": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["@id", "onyx:hasEmotion"]
|
||||||
|
}
|
19
senpy/schemas/emotionPlugin.json
Normal file
19
senpy/schemas/emotionPlugin.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"type": "object",
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "plugin.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"properties": {
|
||||||
|
"onyx:usesEmotionModel": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "emotionModel.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@@ -1,4 +1,24 @@
|
|||||||
{
|
{
|
||||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
"$ref": "definitions.json#/EmotionSet"
|
"properties": {
|
||||||
|
"@id": {"type": "string"},
|
||||||
|
"nif:beginIndex": {"type": "integer"},
|
||||||
|
"nif:endIndex": {"type": "integer"},
|
||||||
|
"nif:anchorOf": {
|
||||||
|
"description": "Piece of context that contains the Sentiment",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"onyx:hasEmotion": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "emotion.json"
|
||||||
|
},
|
||||||
|
"default": []
|
||||||
|
},
|
||||||
|
"prov:wasGeneratedBy": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The ID of the analysis that generated this Emotion. The full object should be included in the \"analysis\" property of the root object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["@id", "prov:wasGeneratedBy", "onyx:hasEmotion"]
|
||||||
}
|
}
|
||||||
|
4
senpy/schemas/entity.json
Normal file
4
senpy/schemas/entity.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"type": "object"
|
||||||
|
}
|
@@ -1,4 +1,39 @@
|
|||||||
{
|
{
|
||||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
"$ref": "definitions.json#/Entry"
|
"name": "Entry",
|
||||||
|
"properties": {
|
||||||
|
"@id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"nif:isString": {
|
||||||
|
"description": "String contained in this Context",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"sentiments": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {"$ref": "sentiment.json" },
|
||||||
|
"default": []
|
||||||
|
},
|
||||||
|
"emotions": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {"$ref": "emotionSet.json" },
|
||||||
|
"default": []
|
||||||
|
},
|
||||||
|
"entities": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {"$ref": "entity.json" },
|
||||||
|
"default": []
|
||||||
|
},
|
||||||
|
"topics": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {"$ref": "topic.json" },
|
||||||
|
"default": []
|
||||||
|
},
|
||||||
|
"suggestions": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {"$ref": "suggestion.json" },
|
||||||
|
"default": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["@id", "nif:isString"]
|
||||||
}
|
}
|
||||||
|
23
senpy/schemas/error.json
Normal file
23
senpy/schemas/error.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"description": "Base schema for all Senpy objects",
|
||||||
|
"type": "object",
|
||||||
|
"allOf": [
|
||||||
|
{"$ref": "atom.json"},
|
||||||
|
{
|
||||||
|
"properties": {
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {"type": "object"}
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["message"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@@ -1,3 +1,19 @@
|
|||||||
{
|
{
|
||||||
"$ref": "definitions.json#/Plugin"
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"type": "object",
|
||||||
|
"required": ["@id", "extra_params"],
|
||||||
|
"properties": {
|
||||||
|
"@id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Unique identifier for the plugin, usually comprised of the name of the plugin and the version."
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The name of the plugin, which will be used in the algorithm detection phase"
|
||||||
|
},
|
||||||
|
"extra_params": {
|
||||||
|
"type": "object",
|
||||||
|
"default": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,18 @@
|
|||||||
{
|
{
|
||||||
"$ref": "definitions.json#/Plugins"
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"allOf": [
|
||||||
|
{"$ref": "response.json"},
|
||||||
|
{
|
||||||
|
"properties": {
|
||||||
|
"plugins": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "plugin.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@type": {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,9 @@
|
|||||||
{
|
{
|
||||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
"$ref": "definitions.json#/Response"
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"@type": {"type": "string"}
|
||||||
|
},
|
||||||
|
"required": ["@type"]
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,39 @@
|
|||||||
{
|
{
|
||||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
"$ref": "definitions.json#/Results"
|
"allOf": [
|
||||||
|
{"$ref": "response.json"},
|
||||||
|
{
|
||||||
|
"title": "Results",
|
||||||
|
"description": "The results of an analysis",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"@context": {
|
||||||
|
"$ref": "context.json"
|
||||||
|
},
|
||||||
|
"@type": {
|
||||||
|
"default": "results"
|
||||||
|
},
|
||||||
|
"@id": {
|
||||||
|
"description": "ID of the analysis",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"analysis": {
|
||||||
|
"type": "array",
|
||||||
|
"default": [],
|
||||||
|
"items": {
|
||||||
|
"$ref": "analysis.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"entries": {
|
||||||
|
"type": "array",
|
||||||
|
"default": [],
|
||||||
|
"items": {
|
||||||
|
"$ref": "entry.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
"required": ["@id", "analysis", "entries"]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,23 @@
|
|||||||
{
|
{
|
||||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
"$ref": "definitions.json#/Sentiment"
|
"properties": {
|
||||||
|
"@id": {"type": "string"},
|
||||||
|
"nif:beginIndex": {"type": "integer"},
|
||||||
|
"nif:endIndex": {"type": "integer"},
|
||||||
|
"nif:anchorOf": {
|
||||||
|
"description": "Piece of context that contains the Sentiment",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"marl:hasPolarity": {
|
||||||
|
"enum": ["marl:Positive", "marl:Negative", "marl:Neutral"]
|
||||||
|
},
|
||||||
|
"marl:polarityValue": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"prov:wasGeneratedBy": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The ID of the analysis that generated this Sentiment. The full object should be included in the \"analysis\" property of the root object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["@id", "prov:wasGeneratedBy"]
|
||||||
}
|
}
|
||||||
|
19
senpy/schemas/sentimentPlugin.json
Normal file
19
senpy/schemas/sentimentPlugin.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"type": "object",
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "plugin.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"properties": {
|
||||||
|
"marl:minPolarityValue": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"marl:maxPolarityValue": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
"$ref": "definitions.json#/Suggestion"
|
"type": "object",
|
||||||
|
"required": ["@id", "prov:wasGeneratedBy"]
|
||||||
}
|
}
|
||||||
|
4
senpy/schemas/topic.json
Normal file
4
senpy/schemas/topic.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"type": "object"
|
||||||
|
}
|
893
senpy/static/css/img/jsoneditor-icons.svg
Normal file
893
senpy/static/css/img/jsoneditor-icons.svg
Normal file
@@ -0,0 +1,893 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="216"
|
||||||
|
height="144"
|
||||||
|
id="svg4136"
|
||||||
|
version="1.1"
|
||||||
|
inkscape:version="0.91 r"
|
||||||
|
sodipodi:docname="jsoneditor-icons.svg">
|
||||||
|
<title
|
||||||
|
id="title6512">JSON Editor Icons</title>
|
||||||
|
<metadata
|
||||||
|
id="metadata4148">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title>JSON Editor Icons</dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<defs
|
||||||
|
id="defs4146" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1"
|
||||||
|
objecttolerance="10"
|
||||||
|
gridtolerance="10"
|
||||||
|
guidetolerance="10"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1028"
|
||||||
|
id="namedview4144"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:zoom="4"
|
||||||
|
inkscape:cx="97.217248"
|
||||||
|
inkscape:cy="59.950227"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg4136"
|
||||||
|
showguides="false"
|
||||||
|
borderlayer="false"
|
||||||
|
inkscape:showpageshadow="true"
|
||||||
|
showborder="true">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid4640"
|
||||||
|
empspacing="24" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<!-- Created with SVG-edit - http://svg-edit.googlecode.com/ -->
|
||||||
|
<g
|
||||||
|
id="g4394">
|
||||||
|
<rect
|
||||||
|
x="4"
|
||||||
|
y="4"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
id="svg_1"
|
||||||
|
style="fill:#1aae1c;fill-opacity:1;stroke:none;stroke-width:0" />
|
||||||
|
<rect
|
||||||
|
style="fill:#ec3f29;fill-opacity:0.94117647;stroke:none;stroke-width:0"
|
||||||
|
x="28.000006"
|
||||||
|
y="3.999995"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
id="svg_1-7" />
|
||||||
|
<rect
|
||||||
|
id="rect4165"
|
||||||
|
height="16"
|
||||||
|
width="16"
|
||||||
|
y="3.999995"
|
||||||
|
x="52.000004"
|
||||||
|
style="fill:#4c4c4c;fill-opacity:1;stroke:none;stroke-width:0" />
|
||||||
|
<rect
|
||||||
|
style="fill:#4c4c4c;fill-opacity:1;stroke:none;stroke-width:0"
|
||||||
|
x="172.00002"
|
||||||
|
y="3.9999852"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
id="rect4175" />
|
||||||
|
<rect
|
||||||
|
style="fill:#4c4c4c;fill-opacity:1;stroke:none;stroke-width:0"
|
||||||
|
x="196"
|
||||||
|
y="3.999995"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
id="rect4175-3" />
|
||||||
|
<g
|
||||||
|
style="stroke:none"
|
||||||
|
id="g4299">
|
||||||
|
<rect
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0"
|
||||||
|
id="svg_1-1"
|
||||||
|
height="1.9999986"
|
||||||
|
width="9.9999924"
|
||||||
|
y="10.999998"
|
||||||
|
x="7.0000048" />
|
||||||
|
<rect
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0"
|
||||||
|
id="svg_1-1-1"
|
||||||
|
height="9.9999838"
|
||||||
|
width="1.9999955"
|
||||||
|
y="7.0000114"
|
||||||
|
x="11.000005" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
style="stroke:none"
|
||||||
|
transform="matrix(0.70710678,-0.70710678,0.70710678,0.70710678,19.029435,12.000001)"
|
||||||
|
id="g4299-3">
|
||||||
|
<rect
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0"
|
||||||
|
id="svg_1-1-0"
|
||||||
|
height="1.9999986"
|
||||||
|
width="9.9999924"
|
||||||
|
y="10.999998"
|
||||||
|
x="7.0000048" />
|
||||||
|
<rect
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0"
|
||||||
|
id="svg_1-1-1-9"
|
||||||
|
height="9.9999838"
|
||||||
|
width="1.9999955"
|
||||||
|
y="7.0000114"
|
||||||
|
x="11.000005" />
|
||||||
|
</g>
|
||||||
|
<rect
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0"
|
||||||
|
x="55.000004"
|
||||||
|
y="7.0000048"
|
||||||
|
width="6.9999909"
|
||||||
|
height="6.9999905"
|
||||||
|
id="svg_1-7-5" />
|
||||||
|
<rect
|
||||||
|
id="rect4354"
|
||||||
|
height="6.9999905"
|
||||||
|
width="6.9999909"
|
||||||
|
y="10.00001"
|
||||||
|
x="58"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:#4c4c4c;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<rect
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:#3c80df;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.94117647"
|
||||||
|
x="58.000004"
|
||||||
|
y="10.000005"
|
||||||
|
width="6.9999909"
|
||||||
|
height="6.9999905"
|
||||||
|
id="svg_1-7-5-7" />
|
||||||
|
<g
|
||||||
|
id="g4378">
|
||||||
|
<rect
|
||||||
|
id="svg_1-7-5-3"
|
||||||
|
height="1.9999965"
|
||||||
|
width="7.9999909"
|
||||||
|
y="10.999999"
|
||||||
|
x="198"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0" />
|
||||||
|
<rect
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0"
|
||||||
|
x="198"
|
||||||
|
y="7.0000005"
|
||||||
|
width="11.999995"
|
||||||
|
height="1.9999946"
|
||||||
|
id="rect4374" />
|
||||||
|
<rect
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0"
|
||||||
|
x="198"
|
||||||
|
y="14.999996"
|
||||||
|
width="3.9999928"
|
||||||
|
height="1.9999995"
|
||||||
|
id="rect4376" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
id="g4383"
|
||||||
|
transform="matrix(1,0,0,-1,-23.999995,23.999995)">
|
||||||
|
<rect
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0"
|
||||||
|
x="198"
|
||||||
|
y="10.999999"
|
||||||
|
width="7.9999909"
|
||||||
|
height="1.9999965"
|
||||||
|
id="rect4385" />
|
||||||
|
<rect
|
||||||
|
id="rect4387"
|
||||||
|
height="1.9999946"
|
||||||
|
width="11.999995"
|
||||||
|
y="7.0000005"
|
||||||
|
x="198"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0" />
|
||||||
|
<rect
|
||||||
|
id="rect4389"
|
||||||
|
height="1.9999995"
|
||||||
|
width="3.9999928"
|
||||||
|
y="14.999996"
|
||||||
|
x="198"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0" />
|
||||||
|
</g>
|
||||||
|
<rect
|
||||||
|
y="3.9999199"
|
||||||
|
x="76"
|
||||||
|
height="16"
|
||||||
|
width="16"
|
||||||
|
id="rect3754-4"
|
||||||
|
style="fill:#4c4c4c;fill-opacity:1;stroke:none" />
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="cccccccc"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path4351"
|
||||||
|
d="m 85.10447,6.0157384 -0.0156,1.4063 c 3.02669,-0.2402 0.33008,3.6507996 2.48438,4.5780996 -2.18694,1.0938 0.49191,4.9069 -2.45313,4.5781 l -0.0156,1.4219 c 5.70828,0.559 1.03264,-5.1005 4.70313,-5.2656 l 0,-1.4063 c -3.61303,-0.027 1.11893,-5.7069996 -4.70313,-5.3124996 z"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="cccccccc"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path4351-9"
|
||||||
|
d="m 82.78125,5.9984384 0.0156,1.4063 c -3.02668,-0.2402 -0.33007,3.6506996 -2.48437,4.5780996 2.18694,1.0938 -0.49192,4.9069 2.45312,4.5781 l 0.0156,1.4219 c -5.70827,0.559 -1.03263,-5.1004 -4.70312,-5.2656 l 0,-1.4063 c 3.61303,-0.027 -1.11894,-5.7070996 4.70312,-5.3124996 z"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<rect
|
||||||
|
y="3.9999199"
|
||||||
|
x="100"
|
||||||
|
height="16"
|
||||||
|
width="16"
|
||||||
|
id="rect3754-25"
|
||||||
|
style="fill:#4c4c4c;fill-opacity:1;stroke:none" />
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path2987"
|
||||||
|
d="m 103.719,5.6719384 0,12.7187996 3.03125,0 0,-1.5313 -1.34375,0 0,-9.6249996 1.375,0 0,-1.5625 z"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:none" />
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path2987-1"
|
||||||
|
d="m 112.2185,5.6721984 0,12.7187996 -3.03125,0 0,-1.5313 1.34375,0 0,-9.6249996 -1.375,0 0,-1.5625 z"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:none" />
|
||||||
|
<rect
|
||||||
|
y="3.9999199"
|
||||||
|
x="124"
|
||||||
|
height="16"
|
||||||
|
width="16"
|
||||||
|
id="rect3754-73"
|
||||||
|
style="fill:#4c4c4c;fill-opacity:1;stroke:none" />
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="ccccccccc"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path3780"
|
||||||
|
d="m 126.2824,17.602938 1.78957,0 1.14143,-2.8641 5.65364,0 1.14856,2.8641 1.76565,0 -4.78687,-11.1610996 -1.91903,0 z"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:none" />
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path3782"
|
||||||
|
d="m 129.72704,13.478838 4.60852,0.01 -2.30426,-5.5497996 z"
|
||||||
|
style="fill:#4c4c4c;fill-opacity:1;stroke:none" />
|
||||||
|
<rect
|
||||||
|
y="3.9999199"
|
||||||
|
x="148"
|
||||||
|
height="16"
|
||||||
|
width="16"
|
||||||
|
id="rect3754-35"
|
||||||
|
style="fill:#4c4c4c;fill-opacity:1;stroke:none" />
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="ccccccc"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path5008-2"
|
||||||
|
d="m 156.47655,5.8917384 0,2.1797 0.46093,2.3983996 1.82813,0 0.39844,-2.3983996 0,-2.1797 z"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:none" />
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="ccccccc"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path5008-2-8"
|
||||||
|
d="m 152.51561,5.8906384 0,2.1797 0.46094,2.3983996 1.82812,0 0.39844,-2.3983996 0,-2.1797 z"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:none" />
|
||||||
|
</g>
|
||||||
|
<rect
|
||||||
|
x="4"
|
||||||
|
y="27.999994"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
id="rect4432"
|
||||||
|
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0" />
|
||||||
|
<rect
|
||||||
|
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0"
|
||||||
|
x="28.000006"
|
||||||
|
y="27.99999"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
id="rect4434" />
|
||||||
|
<rect
|
||||||
|
id="rect4436"
|
||||||
|
height="16"
|
||||||
|
width="16"
|
||||||
|
y="27.99999"
|
||||||
|
x="52.000004"
|
||||||
|
style="fill:#d3d3d3;fill-opacity:1;stroke:#000000;stroke-width:0" />
|
||||||
|
<rect
|
||||||
|
style="fill:#d3d3d3;stroke:#000000;stroke-width:0"
|
||||||
|
x="172.00002"
|
||||||
|
y="27.999981"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
id="rect4446" />
|
||||||
|
<rect
|
||||||
|
style="fill:#d3d3d3;stroke:#000000;stroke-width:0"
|
||||||
|
x="196"
|
||||||
|
y="27.99999"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
id="rect4448" />
|
||||||
|
<g
|
||||||
|
id="g4466"
|
||||||
|
style="stroke:none"
|
||||||
|
transform="translate(0,23.999995)">
|
||||||
|
<rect
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0"
|
||||||
|
id="rect4468"
|
||||||
|
height="1.9999986"
|
||||||
|
width="9.9999924"
|
||||||
|
y="10.999998"
|
||||||
|
x="7.0000048" />
|
||||||
|
<rect
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0"
|
||||||
|
id="rect4470"
|
||||||
|
height="9.9999838"
|
||||||
|
width="1.9999955"
|
||||||
|
y="7.0000114"
|
||||||
|
x="11.000005" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="matrix(0.70710678,-0.70710678,0.70710678,0.70710678,19.029435,35.999996)"
|
||||||
|
id="g4472"
|
||||||
|
style="stroke:none">
|
||||||
|
<rect
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0"
|
||||||
|
id="rect4474"
|
||||||
|
height="1.9999986"
|
||||||
|
width="9.9999924"
|
||||||
|
y="10.999998"
|
||||||
|
x="7.0000048" />
|
||||||
|
<rect
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0"
|
||||||
|
id="rect4476"
|
||||||
|
height="9.9999838"
|
||||||
|
width="1.9999955"
|
||||||
|
y="7.0000114"
|
||||||
|
x="11.000005" />
|
||||||
|
</g>
|
||||||
|
<rect
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0"
|
||||||
|
x="55.000004"
|
||||||
|
y="31"
|
||||||
|
width="6.9999909"
|
||||||
|
height="6.9999905"
|
||||||
|
id="rect4478" />
|
||||||
|
<rect
|
||||||
|
id="rect4480"
|
||||||
|
height="6.9999905"
|
||||||
|
width="6.9999909"
|
||||||
|
y="34.000008"
|
||||||
|
x="58"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:#d3d3d3;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none" />
|
||||||
|
<rect
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:#d3d3d3;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none"
|
||||||
|
x="58.000004"
|
||||||
|
y="34.000004"
|
||||||
|
width="6.9999909"
|
||||||
|
height="6.9999905"
|
||||||
|
id="rect4482" />
|
||||||
|
<g
|
||||||
|
id="g4484"
|
||||||
|
transform="translate(0,23.999995)">
|
||||||
|
<rect
|
||||||
|
id="rect4486"
|
||||||
|
height="1.9999965"
|
||||||
|
width="7.9999909"
|
||||||
|
y="10.999999"
|
||||||
|
x="198"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0" />
|
||||||
|
<rect
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0"
|
||||||
|
x="198"
|
||||||
|
y="7.0000005"
|
||||||
|
width="11.999995"
|
||||||
|
height="1.9999946"
|
||||||
|
id="rect4488" />
|
||||||
|
<rect
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0"
|
||||||
|
x="198"
|
||||||
|
y="14.999996"
|
||||||
|
width="3.9999928"
|
||||||
|
height="1.9999995"
|
||||||
|
id="rect4490" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
id="g4492"
|
||||||
|
transform="matrix(1,0,0,-1,-23.999995,47.99999)">
|
||||||
|
<rect
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0"
|
||||||
|
x="198"
|
||||||
|
y="10.999999"
|
||||||
|
width="7.9999909"
|
||||||
|
height="1.9999965"
|
||||||
|
id="rect4494" />
|
||||||
|
<rect
|
||||||
|
id="rect4496"
|
||||||
|
height="1.9999946"
|
||||||
|
width="11.999995"
|
||||||
|
y="7.0000005"
|
||||||
|
x="198"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0" />
|
||||||
|
<rect
|
||||||
|
id="rect4498"
|
||||||
|
height="1.9999995"
|
||||||
|
width="3.9999928"
|
||||||
|
y="14.999996"
|
||||||
|
x="198"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0" />
|
||||||
|
</g>
|
||||||
|
<rect
|
||||||
|
style="fill:#d3d3d3;fill-opacity:1;stroke:none"
|
||||||
|
id="rect3754-8"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
x="76"
|
||||||
|
y="27.99992" />
|
||||||
|
<path
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 85.10448,30.015537 -0.0156,1.4063 c 3.02668,-0.2402 0.33007,3.6508 2.48438,4.5781 -2.18695,1.0938 0.49191,4.90688 -2.45313,4.57808 l -0.0156,1.4219 c 5.70827,0.559 1.03263,-5.10048 4.70313,-5.26558 l 0,-1.4063 c -3.61304,-0.027 1.11893,-5.707 -4.70313,-5.3125 z"
|
||||||
|
id="path4351-1"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="cccccccc" />
|
||||||
|
<path
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 82.78126,29.998237 0.0156,1.4063 c -3.02668,-0.2402 -0.33008,3.6507 -2.48438,4.5781 2.18694,1.0938 -0.49191,4.90688 2.45313,4.57808 l 0.0156,1.4219 c -5.70828,0.559 -1.03264,-5.10038 -4.70313,-5.26558 l 0,-1.4063 c 3.61303,-0.027 -1.11893,-5.7071 4.70313,-5.3125 z"
|
||||||
|
id="path4351-9-5"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="cccccccc" />
|
||||||
|
<rect
|
||||||
|
style="fill:#d3d3d3;fill-opacity:1;stroke:none"
|
||||||
|
id="rect3754-65"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
x="100"
|
||||||
|
y="27.99992" />
|
||||||
|
<path
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:none"
|
||||||
|
d="m 103.719,29.671937 0,12.71878 3.03125,0 0,-1.5313 -1.34375,0 0,-9.62498 1.375,0 0,-1.5625 z"
|
||||||
|
id="path2987-8"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:none"
|
||||||
|
d="m 112.2185,29.671937 0,12.71878 -3.03125,0 0,-1.5313 1.34375,0 0,-9.62498 -1.375,0 0,-1.5625 z"
|
||||||
|
id="path2987-1-9"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<rect
|
||||||
|
style="fill:#d3d3d3;fill-opacity:1;stroke:none"
|
||||||
|
id="rect3754-92"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
x="124"
|
||||||
|
y="27.99992" />
|
||||||
|
<path
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:none"
|
||||||
|
d="m 126.2824,41.602917 1.78957,0 1.14143,-2.86408 5.65364,0 1.14856,2.86408 1.76565,0 -4.78687,-11.16108 -1.91902,0 z"
|
||||||
|
id="path3780-9"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="ccccccccc" />
|
||||||
|
<path
|
||||||
|
style="fill:#d3d3d3;fill-opacity:1;stroke:none"
|
||||||
|
d="m 129.72704,37.478837 4.60852,0.01 -2.30426,-5.5498 z"
|
||||||
|
id="path3782-2"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<rect
|
||||||
|
style="fill:#d3d3d3;fill-opacity:1;stroke:none"
|
||||||
|
id="rect3754-47"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
x="148"
|
||||||
|
y="27.99992" />
|
||||||
|
<path
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:none"
|
||||||
|
d="m 156.47656,29.891737 0,2.1797 0.46093,2.3984 1.82813,0 0.39844,-2.3984 0,-2.1797 z"
|
||||||
|
id="path5008-2-1"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="ccccccc" />
|
||||||
|
<path
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:none"
|
||||||
|
d="m 152.51562,29.890637 0,2.1797 0.46094,2.3984 1.82812,0 0.39844,-2.3984 0,-2.1797 z"
|
||||||
|
id="path5008-2-8-8"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="ccccccc" />
|
||||||
|
<rect
|
||||||
|
id="svg_1-7-2"
|
||||||
|
height="1.9999961"
|
||||||
|
width="11.999996"
|
||||||
|
y="64"
|
||||||
|
x="54"
|
||||||
|
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0" />
|
||||||
|
<rect
|
||||||
|
id="svg_1-7-2-2"
|
||||||
|
height="2.9999905"
|
||||||
|
width="2.9999907"
|
||||||
|
y="52"
|
||||||
|
x="80.000008"
|
||||||
|
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0" />
|
||||||
|
<rect
|
||||||
|
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
|
||||||
|
x="85.000008"
|
||||||
|
y="52"
|
||||||
|
width="2.9999907"
|
||||||
|
height="2.9999905"
|
||||||
|
id="rect4561" />
|
||||||
|
<rect
|
||||||
|
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
|
||||||
|
x="80.000008"
|
||||||
|
y="58"
|
||||||
|
width="2.9999907"
|
||||||
|
height="2.9999905"
|
||||||
|
id="rect4563" />
|
||||||
|
<rect
|
||||||
|
id="rect4565"
|
||||||
|
height="2.9999905"
|
||||||
|
width="2.9999907"
|
||||||
|
y="58"
|
||||||
|
x="85.000008"
|
||||||
|
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0" />
|
||||||
|
<rect
|
||||||
|
id="rect4567"
|
||||||
|
height="2.9999905"
|
||||||
|
width="2.9999907"
|
||||||
|
y="64"
|
||||||
|
x="80.000008"
|
||||||
|
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0" />
|
||||||
|
<rect
|
||||||
|
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
|
||||||
|
x="85.000008"
|
||||||
|
y="64"
|
||||||
|
width="2.9999907"
|
||||||
|
height="2.9999905"
|
||||||
|
id="rect4569" />
|
||||||
|
<circle
|
||||||
|
style="opacity:1;fill:none;fill-opacity:1;stroke:#4c4c4c;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
|
||||||
|
id="path4571"
|
||||||
|
cx="110.06081"
|
||||||
|
cy="57.939209"
|
||||||
|
r="4.7438836" />
|
||||||
|
<rect
|
||||||
|
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
|
||||||
|
x="116.64566"
|
||||||
|
y="-31.79752"
|
||||||
|
width="4.229713"
|
||||||
|
height="6.4053884"
|
||||||
|
id="rect4563-2"
|
||||||
|
transform="matrix(0.70710678,0.70710678,-0.70710678,0.70710678,0,0)" />
|
||||||
|
<path
|
||||||
|
style="fill:#4c4c4c;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="M 125,56 138.77027,56.095 132,64 Z"
|
||||||
|
id="path4613"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="cccc" />
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="cccc"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path4615"
|
||||||
|
d="M 149,64 162.77027,63.905 156,56 Z"
|
||||||
|
style="fill:#4c4c4c;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<rect
|
||||||
|
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
|
||||||
|
x="54"
|
||||||
|
y="53"
|
||||||
|
width="11.999996"
|
||||||
|
height="1.9999961"
|
||||||
|
id="rect4638" />
|
||||||
|
<rect
|
||||||
|
id="svg_1-7-2-24"
|
||||||
|
height="1.9999957"
|
||||||
|
width="12.99999"
|
||||||
|
y="-56"
|
||||||
|
x="53"
|
||||||
|
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
|
||||||
|
transform="matrix(0,1,-1,0,0,0)" />
|
||||||
|
<rect
|
||||||
|
transform="matrix(0,1,-1,0,0,0)"
|
||||||
|
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
|
||||||
|
x="53"
|
||||||
|
y="-66"
|
||||||
|
width="12.99999"
|
||||||
|
height="1.9999957"
|
||||||
|
id="rect4657" />
|
||||||
|
<rect
|
||||||
|
id="rect4659"
|
||||||
|
height="0.99999291"
|
||||||
|
width="11.999999"
|
||||||
|
y="57"
|
||||||
|
x="54"
|
||||||
|
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0" />
|
||||||
|
<rect
|
||||||
|
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
|
||||||
|
x="54"
|
||||||
|
y="88.000122"
|
||||||
|
width="11.999996"
|
||||||
|
height="1.9999961"
|
||||||
|
id="rect4661" />
|
||||||
|
<rect
|
||||||
|
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
|
||||||
|
x="80.000008"
|
||||||
|
y="76.000122"
|
||||||
|
width="2.9999907"
|
||||||
|
height="2.9999905"
|
||||||
|
id="rect4663" />
|
||||||
|
<rect
|
||||||
|
id="rect4665"
|
||||||
|
height="2.9999905"
|
||||||
|
width="2.9999907"
|
||||||
|
y="76.000122"
|
||||||
|
x="85.000008"
|
||||||
|
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1" />
|
||||||
|
<rect
|
||||||
|
id="rect4667"
|
||||||
|
height="2.9999905"
|
||||||
|
width="2.9999907"
|
||||||
|
y="82.000122"
|
||||||
|
x="80.000008"
|
||||||
|
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1" />
|
||||||
|
<rect
|
||||||
|
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
|
||||||
|
x="85.000008"
|
||||||
|
y="82.000122"
|
||||||
|
width="2.9999907"
|
||||||
|
height="2.9999905"
|
||||||
|
id="rect4669" />
|
||||||
|
<rect
|
||||||
|
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
|
||||||
|
x="80.000008"
|
||||||
|
y="88.000122"
|
||||||
|
width="2.9999907"
|
||||||
|
height="2.9999905"
|
||||||
|
id="rect4671" />
|
||||||
|
<rect
|
||||||
|
id="rect4673"
|
||||||
|
height="2.9999905"
|
||||||
|
width="2.9999907"
|
||||||
|
y="88.000122"
|
||||||
|
x="85.000008"
|
||||||
|
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1" />
|
||||||
|
<circle
|
||||||
|
r="4.7438836"
|
||||||
|
cy="81.939331"
|
||||||
|
cx="110.06081"
|
||||||
|
id="circle4675"
|
||||||
|
style="opacity:1;fill:none;fill-opacity:1;stroke:#d3d3d3;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<rect
|
||||||
|
transform="matrix(0.70710678,0.70710678,-0.70710678,0.70710678,0,0)"
|
||||||
|
id="rect4677"
|
||||||
|
height="6.4053884"
|
||||||
|
width="4.229713"
|
||||||
|
y="-14.826816"
|
||||||
|
x="133.6163"
|
||||||
|
style="fill:#d3d3d3;fill-opacity:1;stroke:#d3d3d3;stroke-width:0;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="cccc"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path4679"
|
||||||
|
d="m 125,80.000005 13.77027,0.09499 L 132,87.999992 Z"
|
||||||
|
style="fill:#d3d3d3;fill-opacity:1;fill-rule:evenodd;stroke:#d3d3d3;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
style="fill:#d3d3d3;fill-opacity:1;fill-rule:evenodd;stroke:#d3d3d3;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="M 149,88.0002 162.77027,87.9052 156,80.0002 Z"
|
||||||
|
id="path4681"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="cccc" />
|
||||||
|
<rect
|
||||||
|
id="rect4683"
|
||||||
|
height="1.9999961"
|
||||||
|
width="11.999996"
|
||||||
|
y="77.000122"
|
||||||
|
x="54"
|
||||||
|
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1" />
|
||||||
|
<rect
|
||||||
|
transform="matrix(0,1,-1,0,0,0)"
|
||||||
|
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
|
||||||
|
x="77.000122"
|
||||||
|
y="-56"
|
||||||
|
width="12.99999"
|
||||||
|
height="1.9999957"
|
||||||
|
id="rect4685" />
|
||||||
|
<rect
|
||||||
|
id="rect4687"
|
||||||
|
height="1.9999957"
|
||||||
|
width="12.99999"
|
||||||
|
y="-66"
|
||||||
|
x="77.000122"
|
||||||
|
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
|
||||||
|
transform="matrix(0,1,-1,0,0,0)" />
|
||||||
|
<rect
|
||||||
|
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
|
||||||
|
x="54"
|
||||||
|
y="81.000122"
|
||||||
|
width="11.999999"
|
||||||
|
height="0.99999291"
|
||||||
|
id="rect4689" />
|
||||||
|
<rect
|
||||||
|
id="rect4761-1"
|
||||||
|
height="1.9999945"
|
||||||
|
width="15.99999"
|
||||||
|
y="101"
|
||||||
|
x="76.000008"
|
||||||
|
style="fill:#ffffff;fill-opacity:0.80000007;stroke:none;stroke-width:0" />
|
||||||
|
<rect
|
||||||
|
id="rect4761-0"
|
||||||
|
height="1.9999945"
|
||||||
|
width="15.99999"
|
||||||
|
y="105"
|
||||||
|
x="76.000008"
|
||||||
|
style="fill:#ffffff;fill-opacity:0.80000007;stroke:none;stroke-width:0" />
|
||||||
|
<rect
|
||||||
|
id="rect4761-7"
|
||||||
|
height="1.9999945"
|
||||||
|
width="9"
|
||||||
|
y="109"
|
||||||
|
x="76.000008"
|
||||||
|
style="fill:#ffffff;fill-opacity:0.80000007;stroke:none;stroke-width:0" />
|
||||||
|
<rect
|
||||||
|
id="rect4761-1-1"
|
||||||
|
height="1.9999945"
|
||||||
|
width="12"
|
||||||
|
y="125"
|
||||||
|
x="76.000008"
|
||||||
|
style="fill:#ffffff;fill-opacity:0.80000007;stroke:none;stroke-width:0" />
|
||||||
|
<rect
|
||||||
|
id="rect4761-1-1-4"
|
||||||
|
height="1.9999945"
|
||||||
|
width="10"
|
||||||
|
y="137"
|
||||||
|
x="76.000008"
|
||||||
|
style="fill:#ffffff;fill-opacity:0.80000007;stroke:none;stroke-width:0" />
|
||||||
|
<rect
|
||||||
|
id="rect4761-1-1-4-4"
|
||||||
|
height="1.9999945"
|
||||||
|
width="10"
|
||||||
|
y="129"
|
||||||
|
x="82"
|
||||||
|
style="fill:#ffffff;fill-opacity:0.80000007;stroke:none;stroke-width:0" />
|
||||||
|
<rect
|
||||||
|
id="rect4761-1-1-4-4-3"
|
||||||
|
height="1.9999945"
|
||||||
|
width="9"
|
||||||
|
y="133"
|
||||||
|
x="82"
|
||||||
|
style="fill:#ffffff;fill-opacity:0.80000007;stroke:none;stroke-width:0" />
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.8;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.66157866;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||||
|
d="m 36.398438,100.0254 c -0.423362,-0.013 -0.846847,0.01 -1.265626,0.062 -1.656562,0.2196 -3.244567,0.9739 -4.507812,2.2266 L 29,100.5991 l -2.324219,7.7129 7.826172,-1.9062 -1.804687,-1.9063 c 1.597702,-1.5308 4.048706,-1.8453 5.984375,-0.7207 1.971162,1.1452 2.881954,3.3975 2.308593,5.5508 -0.573361,2.1533 -2.533865,3.6953 -4.830078,3.6953 l 0,3.0742 c 3.550756,0 6.710442,-2.4113 7.650391,-5.9414 0.939949,-3.5301 -0.618463,-7.2736 -3.710938,-9.0703 -1.159678,-0.6738 -2.431087,-1.0231 -3.701171,-1.0625 z"
|
||||||
|
id="path4138" />
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.8;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.66157866;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||||
|
d="m 59.722656,99.9629 c -1.270084,0.039 -2.541493,0.3887 -3.701172,1.0625 -3.092475,1.7967 -4.650886,5.5402 -3.710937,9.0703 0.939949,3.5301 4.09768,5.9414 7.648437,5.9414 l 0,-3.0742 c -2.296214,0 -4.256717,-1.542 -4.830078,-3.6953 -0.573361,-2.1533 0.337432,-4.4056 2.308594,-5.5508 1.935731,-1.1246 4.38863,-0.8102 5.986326,0.7207 l -1.806638,1.9063 7.828128,1.9062 -2.32422,-7.7129 -1.62696,1.7168 c -1.26338,-1.2531 -2.848917,-2.0088 -4.505855,-2.2285 -0.418778,-0.055 -0.842263,-0.076 -1.265625,-0.062 z"
|
||||||
|
id="path4138-1" />
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="opacity:0.8;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.966;stroke-miterlimit:4;stroke-dasharray:none"
|
||||||
|
d="m 10.5,100 0,2 -2.4999996,0 L 12,107 l 4,-5 -2.5,0 0,-2 -3,0 z"
|
||||||
|
id="path3055-0-77" />
|
||||||
|
<path
|
||||||
|
style="opacity:0.8;fill:none;stroke:#ffffff;stroke-width:1.966;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 4.9850574,108.015 14.0298856,-0.03"
|
||||||
|
id="path5244-5-0-5"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="cc" />
|
||||||
|
<path
|
||||||
|
style="opacity:0.8;fill:none;stroke:#ffffff;stroke-width:1.966;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 4.9849874,132.015 14.0298866,-0.03"
|
||||||
|
id="path5244-5-0-5-8"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="cc" />
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.4;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#4d4d4d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.66157866;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||||
|
d="m 36.398438,123.9629 c -0.423362,-0.013 -0.846847,0.01 -1.265626,0.062 -1.656562,0.2196 -3.244567,0.9739 -4.507812,2.2266 L 29,124.5366 l -2.324219,7.7129 7.826172,-1.9062 -1.804687,-1.9063 c 1.597702,-1.5308 4.048706,-1.8453 5.984375,-0.7207 1.971162,1.1453 2.881954,3.3975 2.308593,5.5508 -0.573361,2.1533 -2.533864,3.6953 -4.830078,3.6953 l 0,3.0742 c 3.550757,0 6.710442,-2.4093 7.650391,-5.9394 0.939949,-3.5301 -0.618463,-7.2756 -3.710938,-9.0723 -1.159678,-0.6737 -2.431087,-1.0231 -3.701171,-1.0625 z"
|
||||||
|
id="path4138-12" />
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.4;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#4d4d4d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.66157866;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||||
|
d="m 59.722656,123.9629 c -1.270084,0.039 -2.541493,0.3888 -3.701172,1.0625 -3.092475,1.7967 -4.650886,5.5422 -3.710937,9.0723 0.939949,3.5301 4.09768,5.9394 7.648437,5.9394 l 0,-3.0742 c -2.296214,0 -4.256717,-1.542 -4.830078,-3.6953 -0.573361,-2.1533 0.337432,-4.4055 2.308594,-5.5508 1.935731,-1.1246 4.38863,-0.8102 5.986326,0.7207 l -1.806638,1.9063 7.828128,1.9062 -2.32422,-7.7129 -1.62696,1.7168 c -1.26338,-1.2531 -2.848917,-2.0088 -4.505855,-2.2285 -0.418778,-0.055 -0.842263,-0.076 -1.265625,-0.062 z"
|
||||||
|
id="path4138-1-3" />
|
||||||
|
<path
|
||||||
|
id="path6191"
|
||||||
|
d="m 10.5,116 0,-2 -2.4999996,0 L 12,109 l 4,5 -2.5,0 0,2 -3,0 z"
|
||||||
|
style="opacity:0.8;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.966;stroke-miterlimit:4;stroke-dasharray:none"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="opacity:0.8;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.966;stroke-miterlimit:4;stroke-dasharray:none"
|
||||||
|
d="m 10.5,129 0,-2 -2.4999996,0 L 12,122 l 4,5 -2.5,0 0,2 -3,0 z"
|
||||||
|
id="path6193" />
|
||||||
|
<path
|
||||||
|
id="path6195"
|
||||||
|
d="m 10.5,135 0,2 -2.4999996,0 L 12,142 l 4,-5 -2.5,0 0,-2 -3,0 z"
|
||||||
|
style="opacity:0.8;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.966;stroke-miterlimit:4;stroke-dasharray:none"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
sodipodi:type="star"
|
||||||
|
style="fill:#4d4d4d;fill-opacity:0.90196078;stroke:#d3d3d3;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
|
||||||
|
id="path4500"
|
||||||
|
sodipodi:sides="3"
|
||||||
|
sodipodi:cx="11.55581"
|
||||||
|
sodipodi:cy="60.073242"
|
||||||
|
sodipodi:r1="5.1116104"
|
||||||
|
sodipodi:r2="2.5558052"
|
||||||
|
sodipodi:arg1="0"
|
||||||
|
sodipodi:arg2="1.0471976"
|
||||||
|
inkscape:flatsided="false"
|
||||||
|
inkscape:rounded="0"
|
||||||
|
inkscape:randomized="0"
|
||||||
|
d="m 16.66742,60.073242 -3.833708,2.213392 -3.8337072,2.213393 0,-4.426785 0,-4.426784 3.8337082,2.213392 z"
|
||||||
|
inkscape:transform-center-x="-1.2779026" />
|
||||||
|
<path
|
||||||
|
inkscape:transform-center-x="1.277902"
|
||||||
|
d="m -31.500004,60.073242 -3.833708,2.213392 -3.833707,2.213393 0,-4.426785 0,-4.426784 3.833707,2.213392 z"
|
||||||
|
inkscape:randomized="0"
|
||||||
|
inkscape:rounded="0"
|
||||||
|
inkscape:flatsided="false"
|
||||||
|
sodipodi:arg2="1.0471976"
|
||||||
|
sodipodi:arg1="0"
|
||||||
|
sodipodi:r2="2.5558052"
|
||||||
|
sodipodi:r1="5.1116104"
|
||||||
|
sodipodi:cy="60.073242"
|
||||||
|
sodipodi:cx="-36.611614"
|
||||||
|
sodipodi:sides="3"
|
||||||
|
id="path4502"
|
||||||
|
style="fill:#4d4d4d;fill-opacity:0.90196078;stroke:#d3d3d3;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
|
||||||
|
sodipodi:type="star"
|
||||||
|
transform="scale(-1,1)" />
|
||||||
|
<path
|
||||||
|
d="m 16.66742,60.073212 -3.833708,2.213392 -3.8337072,2.213392 0,-4.426784 0,-4.426785 3.8337082,2.213392 z"
|
||||||
|
inkscape:randomized="0"
|
||||||
|
inkscape:rounded="0"
|
||||||
|
inkscape:flatsided="false"
|
||||||
|
sodipodi:arg2="1.0471976"
|
||||||
|
sodipodi:arg1="0"
|
||||||
|
sodipodi:r2="2.5558052"
|
||||||
|
sodipodi:r1="5.1116104"
|
||||||
|
sodipodi:cy="60.073212"
|
||||||
|
sodipodi:cx="11.55581"
|
||||||
|
sodipodi:sides="3"
|
||||||
|
id="path4504"
|
||||||
|
style="fill:#4d4d4d;fill-opacity:0.90196078;stroke:#d3d3d3;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
|
||||||
|
sodipodi:type="star"
|
||||||
|
transform="matrix(0,1,-1,0,72.0074,71.7877)"
|
||||||
|
inkscape:transform-center-y="1.2779029" />
|
||||||
|
<path
|
||||||
|
inkscape:transform-center-y="-1.2779026"
|
||||||
|
transform="matrix(0,-1,-1,0,96,96)"
|
||||||
|
sodipodi:type="star"
|
||||||
|
style="fill:#4d4d4d;fill-opacity:0.90196078;stroke:#d3d3d3;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
|
||||||
|
id="path4506"
|
||||||
|
sodipodi:sides="3"
|
||||||
|
sodipodi:cx="11.55581"
|
||||||
|
sodipodi:cy="60.073212"
|
||||||
|
sodipodi:r1="5.1116104"
|
||||||
|
sodipodi:r2="2.5558052"
|
||||||
|
sodipodi:arg1="0"
|
||||||
|
sodipodi:arg2="1.0471976"
|
||||||
|
inkscape:flatsided="false"
|
||||||
|
inkscape:rounded="0"
|
||||||
|
inkscape:randomized="0"
|
||||||
|
d="m 16.66742,60.073212 -3.833708,2.213392 -3.8337072,2.213392 0,-4.426784 0,-4.426785 3.8337082,2.213392 z" />
|
||||||
|
<path
|
||||||
|
sodipodi:nodetypes="cccc"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path4615-5"
|
||||||
|
d="m 171.82574,65.174193 16.34854,0 -8.17427,-13.348454 z"
|
||||||
|
style="fill:#fbb917;fill-opacity:1;fill-rule:evenodd;stroke:#fbb917;stroke-width:1.65161395;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="m 179,55 0,6 2,0 0,-6"
|
||||||
|
id="path4300"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="cccc" />
|
||||||
|
<path
|
||||||
|
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="m 179,62 0,2 2,0 0,-2"
|
||||||
|
id="path4300-6"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="cccc" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 35 KiB |
449
senpy/static/css/jsoneditor.css
Normal file
449
senpy/static/css/jsoneditor.css
Normal file
@@ -0,0 +1,449 @@
|
|||||||
|
|
||||||
|
div.jsoneditor {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor-field,
|
||||||
|
div.jsoneditor-value,
|
||||||
|
div.jsoneditor-readonly {
|
||||||
|
border: 1px solid transparent;
|
||||||
|
min-height: 16px;
|
||||||
|
min-width: 32px;
|
||||||
|
padding: 2px;
|
||||||
|
margin: 1px;
|
||||||
|
word-wrap: break-word;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* adjust margin of p elements inside editable divs, needed for Opera, IE */
|
||||||
|
div.jsoneditor-field p,
|
||||||
|
div.jsoneditor-value p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor-value {
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor-readonly {
|
||||||
|
min-width: 16px;
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor-empty {
|
||||||
|
border-color: lightgray;
|
||||||
|
border-style: dashed;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor-field.jsoneditor-empty::after,
|
||||||
|
div.jsoneditor-value.jsoneditor-empty::after {
|
||||||
|
pointer-events: none;
|
||||||
|
color: lightgray;
|
||||||
|
font-size: 8pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor-field.jsoneditor-empty::after {
|
||||||
|
content: "field";
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor-value.jsoneditor-empty::after {
|
||||||
|
content: "value";
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor-value.jsoneditor-url,
|
||||||
|
a.jsoneditor-value.jsoneditor-url {
|
||||||
|
color: green;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.jsoneditor-value.jsoneditor-url {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 2px;
|
||||||
|
margin: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.jsoneditor-value.jsoneditor-url:hover,
|
||||||
|
a.jsoneditor-value.jsoneditor-url:focus {
|
||||||
|
color: #ee422e;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor td.jsoneditor-separator {
|
||||||
|
padding: 3px 0;
|
||||||
|
vertical-align: top;
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor-field[contenteditable=true]:focus,
|
||||||
|
div.jsoneditor-field[contenteditable=true]:hover,
|
||||||
|
div.jsoneditor-value[contenteditable=true]:focus,
|
||||||
|
div.jsoneditor-value[contenteditable=true]:hover,
|
||||||
|
div.jsoneditor-field.jsoneditor-highlight,
|
||||||
|
div.jsoneditor-value.jsoneditor-highlight {
|
||||||
|
background-color: #FFFFAB;
|
||||||
|
border: 1px solid yellow;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor-field.jsoneditor-highlight-active,
|
||||||
|
div.jsoneditor-field.jsoneditor-highlight-active:focus,
|
||||||
|
div.jsoneditor-field.jsoneditor-highlight-active:hover,
|
||||||
|
div.jsoneditor-value.jsoneditor-highlight-active,
|
||||||
|
div.jsoneditor-value.jsoneditor-highlight-active:focus,
|
||||||
|
div.jsoneditor-value.jsoneditor-highlight-active:hover {
|
||||||
|
background-color: #ffee00;
|
||||||
|
border: 1px solid #ffc700;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor-value.jsoneditor-string {
|
||||||
|
color: #008000;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor-value.jsoneditor-object,
|
||||||
|
div.jsoneditor-value.jsoneditor-array {
|
||||||
|
min-width: 16px;
|
||||||
|
color: #808080;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor-value.jsoneditor-number {
|
||||||
|
color: #ee422e;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor-value.jsoneditor-boolean {
|
||||||
|
color: #ff8c00;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor-value.jsoneditor-null {
|
||||||
|
color: #004ED0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor-value.jsoneditor-invalid {
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
div.jsoneditor-tree button {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
background: transparent url('img/jsoneditor-icons.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor-mode-view tr.jsoneditor-expandable td.jsoneditor-tree,
|
||||||
|
div.jsoneditor-mode-form tr.jsoneditor-expandable td.jsoneditor-tree {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor-tree button.jsoneditor-collapsed {
|
||||||
|
background-position: 0 -48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor-tree button.jsoneditor-expanded {
|
||||||
|
background-position: 0 -72px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor-tree button.jsoneditor-contextmenu {
|
||||||
|
background-position: -48px -72px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor-tree button.jsoneditor-contextmenu:hover,
|
||||||
|
div.jsoneditor-tree button.jsoneditor-contextmenu:focus,
|
||||||
|
div.jsoneditor-tree button.jsoneditor-contextmenu.jsoneditor-selected,
|
||||||
|
tr.jsoneditor-selected.jsoneditor-first button.jsoneditor-contextmenu {
|
||||||
|
background-position: -48px -48px;
|
||||||
|
}
|
||||||
|
div.jsoneditor-menu {
|
||||||
|
display:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor-tree *:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor-tree button:focus {
|
||||||
|
/* TODO: nice outline for buttons with focus
|
||||||
|
outline: #97B0F8 solid 2px;
|
||||||
|
box-shadow: 0 0 8px #97B0F8;
|
||||||
|
*/
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
outline: #e5e5e5 solid 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor-tree button.jsoneditor-invisible {
|
||||||
|
visibility: hidden;
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor {
|
||||||
|
color: #1A1A1A;
|
||||||
|
border: 0px solid #3883fa;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
padding: 0;
|
||||||
|
line-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
div.jsoneditor-tree table.jsoneditor-tree {
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor-outer {
|
||||||
|
position: static;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: -35px 0 0 0;
|
||||||
|
padding: 35px 0 0 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea.jsoneditor-text,
|
||||||
|
.ace-jsoneditor {
|
||||||
|
min-height: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor-tree {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea.jsoneditor-text {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
outline-width: 0;
|
||||||
|
border: none;
|
||||||
|
background-color: white;
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr.jsoneditor-highlight,
|
||||||
|
tr.jsoneditor-selected {
|
||||||
|
background-color: #e6e6e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr.jsoneditor-selected button.jsoneditor-dragarea,
|
||||||
|
tr.jsoneditor-selected button.jsoneditor-contextmenu {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr.jsoneditor-selected.jsoneditor-first button.jsoneditor-dragarea,
|
||||||
|
tr.jsoneditor-selected.jsoneditor-first button.jsoneditor-contextmenu {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor-tree button.jsoneditor-dragarea {
|
||||||
|
background: url('img/jsoneditor-icons.svg') -72px -72px;
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor-tree button.jsoneditor-dragarea:hover,
|
||||||
|
div.jsoneditor-tree button.jsoneditor-dragarea:focus,
|
||||||
|
tr.jsoneditor-selected.jsoneditor-first button.jsoneditor-dragarea {
|
||||||
|
background-position: -72px -48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor tr,
|
||||||
|
div.jsoneditor th,
|
||||||
|
div.jsoneditor td {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor td {
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor td.jsoneditor-tree {
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor-field,
|
||||||
|
div.jsoneditor-value,
|
||||||
|
div.jsoneditor td,
|
||||||
|
div.jsoneditor th,
|
||||||
|
div.jsoneditor textarea,
|
||||||
|
.jsoneditor-schema-error {
|
||||||
|
font-family: droid sans mono, consolas, monospace, courier new, courier, sans-serif;
|
||||||
|
font-size: 10pt;
|
||||||
|
color: #1A1A1A;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* popover */
|
||||||
|
.jsoneditor-schema-error {
|
||||||
|
cursor: default;
|
||||||
|
display: inline-block;
|
||||||
|
/*font-family: arial, sans-serif;*/
|
||||||
|
height: 24px;
|
||||||
|
line-height: 24px;
|
||||||
|
position: relative;
|
||||||
|
text-align: center;
|
||||||
|
width: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor-tree .jsoneditor-schema-error {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0 4px 0 0;
|
||||||
|
background: url('img/jsoneditor-icons.svg') -168px -48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jsoneditor-schema-error .jsoneditor-popover {
|
||||||
|
background-color: #4c4c4c;
|
||||||
|
border-radius: 3px;
|
||||||
|
box-shadow: 0 0 5px rgba(0,0,0,0.4);
|
||||||
|
color: #fff;
|
||||||
|
display: none;
|
||||||
|
padding: 7px 10px;
|
||||||
|
position: absolute;
|
||||||
|
width: 200px;
|
||||||
|
z-index: 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-above {
|
||||||
|
bottom: 32px;
|
||||||
|
left: -98px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-below {
|
||||||
|
top: 32px;
|
||||||
|
left: -98px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-left {
|
||||||
|
top: -7px;
|
||||||
|
right: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-right {
|
||||||
|
top: -7px;
|
||||||
|
left: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jsoneditor-schema-error .jsoneditor-popover:before {
|
||||||
|
border-right: 7px solid transparent;
|
||||||
|
border-left: 7px solid transparent;
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -7px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-above:before {
|
||||||
|
border-top: 7px solid #4c4c4c;
|
||||||
|
bottom: -7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-below:before {
|
||||||
|
border-bottom: 7px solid #4c4c4c;
|
||||||
|
top: -7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-left:before {
|
||||||
|
border-left: 7px solid #4c4c4c;
|
||||||
|
border-top: 7px solid transparent;
|
||||||
|
border-bottom: 7px solid transparent;
|
||||||
|
content: '';
|
||||||
|
top: 19px;
|
||||||
|
right: -14px;
|
||||||
|
left: inherit;
|
||||||
|
margin-left: inherit;
|
||||||
|
margin-top: -7px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-right:before {
|
||||||
|
border-right: 7px solid #4c4c4c;
|
||||||
|
border-top: 7px solid transparent;
|
||||||
|
border-bottom: 7px solid transparent;
|
||||||
|
content: '';
|
||||||
|
top: 19px;
|
||||||
|
left: -14px;
|
||||||
|
margin-left: inherit;
|
||||||
|
margin-top: -7px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jsoneditor-schema-error:hover .jsoneditor-popover,
|
||||||
|
.jsoneditor-schema-error:focus .jsoneditor-popover {
|
||||||
|
display: block;
|
||||||
|
-webkit-animation: fade-in .3s linear 1, move-up .3s linear 1;
|
||||||
|
-moz-animation: fade-in .3s linear 1, move-up .3s linear 1;
|
||||||
|
-ms-animation: fade-in .3s linear 1, move-up .3s linear 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes fade-in {
|
||||||
|
from { opacity: 0; }
|
||||||
|
to { opacity: 1; }
|
||||||
|
}
|
||||||
|
@-moz-keyframes fade-in {
|
||||||
|
from { opacity: 0; }
|
||||||
|
to { opacity: 1; }
|
||||||
|
}
|
||||||
|
@-ms-keyframes fade-in {
|
||||||
|
from { opacity: 0; }
|
||||||
|
to { opacity: 1; }
|
||||||
|
}
|
||||||
|
/*@-webkit-keyframes move-up {*/
|
||||||
|
/*from { bottom: 24px; }*/
|
||||||
|
/*to { bottom: 32px; }*/
|
||||||
|
/*}*/
|
||||||
|
/*@-moz-keyframes move-up {*/
|
||||||
|
/*from { bottom: 24px; }*/
|
||||||
|
/*to { bottom: 32px; }*/
|
||||||
|
/*}*/
|
||||||
|
/*@-ms-keyframes move-up {*/
|
||||||
|
/*from { bottom: 24px; }*/
|
||||||
|
/*to { bottom: 32px; }*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
|
||||||
|
/* JSON schema errors displayed at the bottom of the editor in mode text and code */
|
||||||
|
|
||||||
|
.jsoneditor .jsoneditor-text-errors {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
background-color: #ffef8b;
|
||||||
|
border-top: 1px solid #ffd700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jsoneditor .jsoneditor-text-errors td {
|
||||||
|
padding: 3px 6px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jsoneditor-text-errors .jsoneditor-schema-error {
|
||||||
|
border: none;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0 4px 0 0;
|
||||||
|
background: url('img/jsoneditor-icons.svg') -168px -48px;
|
||||||
|
}
|
||||||
|
|
@@ -8,7 +8,7 @@ body {
|
|||||||
}
|
}
|
||||||
#inputswrapper {
|
#inputswrapper {
|
||||||
min-height:100%;
|
min-height:100%;
|
||||||
background: white;
|
/* background: white; */
|
||||||
position:relative;
|
position:relative;
|
||||||
min-width: 800px;
|
min-width: 800px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -50,23 +50,31 @@ body {
|
|||||||
#form {
|
#form {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
#results {
|
.results {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 20px;
|
/* padding: 20px; */
|
||||||
background: lightgray;
|
background: white;
|
||||||
-moz-border-radius: 20px;
|
/* -moz-border-radius: 20px; */
|
||||||
-webkit-border-radius: 20px;
|
/* -webkit-border-radius: 20px; */
|
||||||
-khtml-border-radius: 20px;
|
/* -khtml-border-radius: 20px; */
|
||||||
border-radius: 20px;
|
/* border-radius: 20px; */
|
||||||
|
|
||||||
}
|
}
|
||||||
#input_request {
|
#input_request {
|
||||||
|
margin-top: 5px;
|
||||||
display:block;
|
display:block;
|
||||||
width:150px;
|
|
||||||
word-wrap:break-word;
|
word-wrap:break-word;
|
||||||
|
white-space:pre;
|
||||||
|
overflow: auto;
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
textarea
|
#input_request a{
|
||||||
{
|
width:100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
textarea{
|
||||||
border:1px solid #999999;
|
border:1px solid #999999;
|
||||||
width:100%;
|
width:100%;
|
||||||
margin:5px 0;
|
margin:5px 0;
|
||||||
@@ -139,3 +147,8 @@ textarea
|
|||||||
#header {
|
#header {
|
||||||
font-family: 'Architects Daughter', cursive;
|
font-family: 'Architects Daughter', cursive;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#results-div {
|
||||||
|
/* background: white; */
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
36377
senpy/static/js/jsoneditor.js
Normal file
36377
senpy/static/js/jsoneditor.js
Normal file
File diff suppressed because one or more lines are too long
@@ -29,45 +29,60 @@ $(document).ready(function() {
|
|||||||
var response = JSON.parse($.ajax({type: "GET", url: "/api/plugins/" , async: false}).responseText);
|
var response = JSON.parse($.ajax({type: "GET", url: "/api/plugins/" , async: false}).responseText);
|
||||||
var defaultPlugin= JSON.parse($.ajax({type: "GET", url: "/api/plugins/default" , async: false}).responseText);
|
var defaultPlugin= JSON.parse($.ajax({type: "GET", url: "/api/plugins/default" , async: false}).responseText);
|
||||||
html="";
|
html="";
|
||||||
|
var availablePlugins = document.getElementById('availablePlugins');
|
||||||
plugins = response.plugins;
|
plugins = response.plugins;
|
||||||
for (r in plugins){
|
for (r in plugins){
|
||||||
if (plugins[r]["name"]){
|
plugin = plugins[r]
|
||||||
if (plugins[r]["name"] == defaultPlugin["name"]){
|
if (plugin["name"]){
|
||||||
if (plugins[r]["is_activated"]){
|
if (plugin["name"] == defaultPlugin["name"]){
|
||||||
html+= "<option value=\""+plugins[r]["name"]+"\" selected=\"selected\">"+plugins[r]["name"]+"</option>"
|
if (plugin["is_activated"]){
|
||||||
|
html+= "<option value=\""+plugin["name"]+"\" selected=\"selected\">"+plugin["name"]+"</option>"
|
||||||
}else{
|
}else{
|
||||||
html+= "<option value=\""+plugins[r]["name"]+"\" selected=\"selected\" disabled=\"disabled\">"+plugins[r]["name"]+"</option>"
|
html+= "<option value=\""+plugin["name"]+"\" selected=\"selected\" disabled=\"disabled\">"+plugin["name"]+"</option>"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
if (plugins[r]["is_activated"]){
|
if (plugin["is_activated"]){
|
||||||
html+= "<option value=\""+plugins[r]["name"]+"\">"+plugins[r]["name"]+"</option>"
|
html+= "<option value=\""+plugin["name"]+"\">"+plugin["name"]+"</option>"
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
html+= "<option value=\""+plugins[r]["name"]+"\" disabled=\"disabled\">"+plugins[r]["name"]+"</option>"
|
html+= "<option value=\""+plugin["name"]+"\" disabled=\"disabled\">"+plugin["name"]+"</option>"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (plugins[r]["extra_params"]){
|
if (plugin["extra_params"]){
|
||||||
plugins_params[plugins[r]["name"]]={};
|
plugins_params[plugin["name"]]={};
|
||||||
for (param in plugins[r]["extra_params"]){
|
for (param in plugin["extra_params"]){
|
||||||
if (typeof plugins[r]["extra_params"][param] !="string"){
|
if (typeof plugin["extra_params"][param] !="string"){
|
||||||
var params = new Array();
|
var params = new Array();
|
||||||
var alias = plugins[r]["extra_params"][param]["aliases"][0];
|
var alias = plugin["extra_params"][param]["aliases"][0];
|
||||||
params[alias]=new Array();
|
params[alias]=new Array();
|
||||||
for (option in plugins[r]["extra_params"][param]["options"]){
|
for (option in plugin["extra_params"][param]["options"]){
|
||||||
params[alias].push(plugins[r]["extra_params"][param]["options"][option])
|
params[alias].push(plugin["extra_params"][param]["options"][option])
|
||||||
}
|
}
|
||||||
plugins_params[plugins[r]["name"]][alias] = (params[alias])
|
plugins_params[plugin["name"]][alias] = (params[alias])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var pluginList = document.createElement('li');
|
||||||
|
|
||||||
|
newHtml = ""
|
||||||
|
if(plugin.url) {
|
||||||
|
newHtml= "<a href="+plugin.url+">" + plugin.name + "</a>";
|
||||||
|
}else {
|
||||||
|
newHtml= plugin["name"];
|
||||||
|
}
|
||||||
|
newHtml += ": " + replaceURLWithHTMLLinks(plugin.description);
|
||||||
|
pluginList.innerHTML = newHtml;
|
||||||
|
availablePlugins.appendChild(pluginList)
|
||||||
}
|
}
|
||||||
document.getElementById('plugins').innerHTML = html;
|
document.getElementById('plugins').innerHTML = html;
|
||||||
change_params();
|
change_params();
|
||||||
|
|
||||||
$(window).on('hashchange', hashchanged);
|
$(window).on('hashchange', hashchanged);
|
||||||
hashchanged();
|
hashchanged();
|
||||||
$('.tooltip-form').tooltip();
|
$('.tooltip-form').tooltip();
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -90,6 +105,10 @@ function change_params(){
|
|||||||
|
|
||||||
function load_JSON(){
|
function load_JSON(){
|
||||||
url = "/api";
|
url = "/api";
|
||||||
|
var container = document.getElementById('results');
|
||||||
|
var rawcontainer = document.getElementById("jsonraw");
|
||||||
|
rawcontainer.innerHTML = '';
|
||||||
|
container.innerHTML = '';
|
||||||
var plugin = document.getElementById("plugins").options[document.getElementById("plugins").selectedIndex].value;
|
var plugin = document.getElementById("plugins").options[document.getElementById("plugins").selectedIndex].value;
|
||||||
var input = encodeURIComponent(document.getElementById("input").value);
|
var input = encodeURIComponent(document.getElementById("input").value);
|
||||||
url += "?algo="+plugin+"&i="+input
|
url += "?algo="+plugin+"&i="+input
|
||||||
@@ -102,8 +121,15 @@ function load_JSON(){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
var response = JSON.parse($.ajax({type: "GET", url: url , async: false}).responseText);
|
var response = JSON.parse($.ajax({type: "GET", url: url , async: false}).responseText);
|
||||||
document.getElementById("results").innerHTML = replaceURLWithHTMLLinks(JSON.stringify(response, undefined, 2))
|
var options = {
|
||||||
document.getElementById("input_request").innerHTML = "<label>"+url+"</label>"
|
mode: 'view'
|
||||||
|
};
|
||||||
|
var editor = new JSONEditor(container, options, response);
|
||||||
|
editor.expandAll();
|
||||||
|
rawcontainer.innerHTML = replaceURLWithHTMLLinks(JSON.stringify(response, undefined, 2))
|
||||||
|
document.getElementById("input_request").innerHTML = "<a href='"+url+"'>"+url+"</a>"
|
||||||
|
document.getElementById("results-div").style.display = 'block';
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||||
<title>Playground</title>
|
<title>Playground {{version}}</title>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<script src="static/js/jquery-2.1.1.min.js" ></script>
|
<script src="static/js/jquery-2.1.1.min.js" ></script>
|
||||||
@@ -10,46 +10,75 @@
|
|||||||
<link rel="stylesheet" href="static/css/bootstrap.min.css">
|
<link rel="stylesheet" href="static/css/bootstrap.min.css">
|
||||||
<link rel="stylesheet" href="static/css/main.css">
|
<link rel="stylesheet" href="static/css/main.css">
|
||||||
<link rel="stylesheet" href="static/font-awesome-4.1.0/css/font-awesome.min.css">
|
<link rel="stylesheet" href="static/font-awesome-4.1.0/css/font-awesome.min.css">
|
||||||
|
<link href="static/css/jsoneditor.css" rel="stylesheet" type="text/css">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<script src="static/js/bootstrap.min.js"></script>
|
<script src="static/js/bootstrap.min.js"></script>
|
||||||
|
<script src="static/js/jsoneditor.js"></script>
|
||||||
<script src="static/js/main.js"></script>
|
<script src="static/js/main.js"></script>
|
||||||
|
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div id="header">
|
<div id="header">
|
||||||
<h3 id="header-title">
|
<h3 id="header-title">
|
||||||
<a href="https://github.com/gsi-upm/senpy" target="_blank">
|
<a href="https://github.com/gsi-upm/senpy" target="_blank">
|
||||||
<img id="header-logo" class="imsg-responsive" src="static/img/header.png"/></a> Playground
|
<img id="header-logo" class="imsg-responsive" src="static/img/header.png"/></a> Playground
|
||||||
|
|
||||||
</h3>
|
</h3>
|
||||||
|
<h4>v{{ version}}</h4>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="nav nav-tabs" role="tablist">
|
<ul class="nav nav-tabs" role="tablist">
|
||||||
<li role="presentation" class="active"><a class="active" href="#about">About</a></li>
|
<li role="presentation" ><a class="active" href="#about">About</a></li>
|
||||||
<li role="presentation"><a class="active" href="#test">Test it</a></li>
|
<li role="presentation"class="active"><a class="active" href="#test">Test it</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
<div class="tab-pane active" id="about">
|
<div class="tab-pane" id="about">
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-6">
|
<div class="col-lg-6">
|
||||||
<div class="well">
|
<h2>About Senpy</h2>
|
||||||
<h2>Test Senpy</h2>
|
<p>Senpy is a framework to build semantic sentiment and emotion analysis services. It does so by using a mix of web and semantic technologies, such as JSON-LD, RDFlib and Flask.</p>
|
||||||
<div>
|
<p>Senpy makes it easy to develop and publish your own analysis algorithms (plugins in senpy terms).
|
||||||
<p class="text-center">
|
</p>
|
||||||
<a class="btn btn-lg btn-primary" href="#test" role="button">Test it »</a>
|
<p>
|
||||||
|
This website is the senpy Playground, which allows you to test the instance of senpy in this server. It provides a user-friendly interface to the functions exposed by the senpy API.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Once you get comfortable with the parameters and results, you are encouraged to issue your own requests to the API endpoint, which should be <a href="/api">here</a>.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
These are some of the things you can do with the API:
|
||||||
|
<ul>
|
||||||
|
<li>List all available plugins: <a href="/api/plugins">/api/plugins</a></li>
|
||||||
|
<li>Get information about the default plugin: <a href="/api/plugins/default">/api/plugins/default</a></li>
|
||||||
|
<li>Download the JSON-LD context used: <a href="/api/contexts/Results.jsonld">/api/contexts/Results.jsonld</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-lg-6 ">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
Available Plugins
|
||||||
|
</div>
|
||||||
|
<div class="panel-body"><ul id=availablePlugins></ul></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-6 ">
|
<div class="col-lg-6 ">
|
||||||
|
<a href="http://senpy.readthedocs.io">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading"><i class="fa fa-sign-in"></i> Follow us on <a href="http://www.github.com/gsi-upm/senpy">GitHub</a></div>
|
<div class="panel-heading"><i class="fa fa-book"></i> If you are new to senpy, you might want to read senpy's documentation</div>
|
||||||
</div>
|
</div>
|
||||||
|
</a>
|
||||||
|
<a href="http://www.github.com/gsi-upm/senpy">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading"><i class="fa fa-sign-in"></i> Feel free to follow us on GitHub</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading"><i class="fa fa-child"></i> Enjoy.</div>
|
<div class="panel-heading"><i class="fa fa-child"></i> Enjoy.</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -57,7 +86,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tab-pane" id="test">
|
<div class="tab-pane active" id="test">
|
||||||
<div class="well">
|
<div class="well">
|
||||||
<form id="form" onsubmit="return getPlugins();" accept-charset="utf-8">
|
<form id="form" onsubmit="return getPlugins();" accept-charset="utf-8">
|
||||||
<div id="inputswrapper">
|
<div id="inputswrapper">
|
||||||
@@ -71,12 +100,29 @@ I cannot believe it!</textarea></div>
|
|||||||
<div id ="params">
|
<div id ="params">
|
||||||
</div>
|
</div>
|
||||||
</br>
|
</br>
|
||||||
<a id="preview" class="btn btn-lg btn-primary" href="#" onclick="load_JSON()">Analyse!</a>
|
<a id="preview" class="btn btn-lg btn-primary" onclick="load_JSON()">Analyse!</a>
|
||||||
<!--<button id="visualise" name="type" type="button">Visualise!</button>-->
|
<!--<button id="visualise" name="type" type="button">Visualise!</button>-->
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<div id="content">
|
</div>
|
||||||
<span id="input_request"></span>
|
<span id="input_request"></span>
|
||||||
<pre id="results"></pre>
|
<div id="results-div">
|
||||||
|
<ul class="nav nav-tabs" role="tablist">
|
||||||
|
<li role="presentation" class="active"><a class="active" href="#viewer">Viewer</a></li>
|
||||||
|
<li role="presentation"><a class="active" href="#raw">Raw</a></li>
|
||||||
|
</ul>
|
||||||
|
<div class="tab-content" id="results-container">
|
||||||
|
|
||||||
|
<div class="tab-pane active" id="viewer">
|
||||||
|
<div id="content">
|
||||||
|
<pre id="results" class="results"></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-pane" id="raw">
|
||||||
|
<div id="content">
|
||||||
|
<pre id="jsonraw" class="results"></pre>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
15
senpy/version.py
Normal file
15
senpy/version.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
ROOT = os.path.dirname(__file__)
|
||||||
|
DEFAULT_FILE = os.path.join(ROOT, 'VERSION')
|
||||||
|
|
||||||
|
|
||||||
|
def read_version(versionfile=DEFAULT_FILE):
|
||||||
|
with open(versionfile) as f:
|
||||||
|
return f.read().strip()
|
||||||
|
|
||||||
|
|
||||||
|
__version__ = read_version()
|
@@ -2,3 +2,11 @@
|
|||||||
description-file = README.rst
|
description-file = README.rst
|
||||||
[aliases]
|
[aliases]
|
||||||
test=pytest
|
test=pytest
|
||||||
|
[flake8]
|
||||||
|
# because of the way that future works, we need to call install_aliases before
|
||||||
|
# finishing the imports. flake8 thinks that we're doing the imports too late,
|
||||||
|
# but it's actually ok
|
||||||
|
ignore = E402
|
||||||
|
max-line-length = 100
|
||||||
|
[bdist_wheel]
|
||||||
|
universal=1
|
32
setup.py
32
setup.py
@@ -1,35 +1,34 @@
|
|||||||
import pip
|
import pip
|
||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
from pip.req import parse_requirements
|
|
||||||
# parse_requirements() returns generator of pip.req.InstallRequirement objects
|
# parse_requirements() returns generator of pip.req.InstallRequirement objects
|
||||||
|
from pip.req import parse_requirements
|
||||||
|
from senpy import __version__
|
||||||
|
|
||||||
try:
|
try:
|
||||||
install_reqs = parse_requirements("requirements.txt", session=pip.download.PipSession())
|
install_reqs = parse_requirements(
|
||||||
test_reqs = parse_requirements("test-requirements.txt", session=pip.download.PipSession())
|
"requirements.txt", session=pip.download.PipSession())
|
||||||
|
test_reqs = parse_requirements(
|
||||||
|
"test-requirements.txt", session=pip.download.PipSession())
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
install_reqs = parse_requirements("requirements.txt")
|
install_reqs = parse_requirements("requirements.txt")
|
||||||
test_reqs = parse_requirements("test-requirements.txt")
|
test_reqs = parse_requirements("test-requirements.txt")
|
||||||
|
|
||||||
# reqs is a list of requirement
|
|
||||||
# e.g. ['django==1.5.1', 'mezzanine==1.4.6']
|
|
||||||
install_reqs = [str(ir.req) for ir in install_reqs]
|
install_reqs = [str(ir.req) for ir in install_reqs]
|
||||||
test_reqs = [str(ir.req) for ir in test_reqs]
|
test_reqs = [str(ir.req) for ir in test_reqs]
|
||||||
|
|
||||||
with open('senpy/VERSION') as f:
|
|
||||||
__version__ = f.read().strip()
|
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='senpy',
|
name='senpy',
|
||||||
packages=['senpy'], # this must be the same as the name above
|
packages=['senpy'], # this must be the same as the name above
|
||||||
version=__version__,
|
version=__version__,
|
||||||
description='''
|
description=('A sentiment analysis server implementation. '
|
||||||
A sentiment analysis server implementation. Designed to be \
|
'Designed to be extensible, so new algorithms '
|
||||||
extendable, so new algorithms and sources can be used.
|
'and sources can be used.'),
|
||||||
''',
|
|
||||||
author='J. Fernando Sanchez',
|
author='J. Fernando Sanchez',
|
||||||
author_email='balkian@gmail.com',
|
author_email='balkian@gmail.com',
|
||||||
url='https://github.com/gsi-upm/senpy', # use the URL to the github repo
|
url='https://github.com/gsi-upm/senpy', # use the URL to the github repo
|
||||||
download_url='https://github.com/gsi-upm/senpy/archive/{}.tar.gz' .format(__version__),
|
download_url='https://github.com/gsi-upm/senpy/archive/{}.tar.gz'.format(
|
||||||
|
__version__),
|
||||||
keywords=['eurosentiment', 'sentiment', 'emotions', 'nif'],
|
keywords=['eurosentiment', 'sentiment', 'emotions', 'nif'],
|
||||||
classifiers=[],
|
classifiers=[],
|
||||||
install_requires=install_reqs,
|
install_requires=install_reqs,
|
||||||
@@ -37,9 +36,6 @@ extendable, so new algorithms and sources can be used.
|
|||||||
setup_requires=['pytest-runner', ],
|
setup_requires=['pytest-runner', ],
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
entry_points={
|
entry_points={
|
||||||
'console_scripts': [
|
'console_scripts':
|
||||||
'senpy = senpy.__main__:main',
|
['senpy = senpy.__main__:main', 'senpy-cli = senpy.cli:main']
|
||||||
'senpy-cli = senpy.cli:main'
|
})
|
||||||
]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
@@ -1,2 +1,3 @@
|
|||||||
pytest
|
pytest
|
||||||
mock
|
mock
|
||||||
|
pytest-cov
|
||||||
|
@@ -1,8 +0,0 @@
|
|||||||
from senpy.plugins import SentimentPlugin
|
|
||||||
from senpy.models import Results
|
|
||||||
|
|
||||||
|
|
||||||
class DummyPlugin(SentimentPlugin):
|
|
||||||
|
|
||||||
def analyse(self, *args, **kwargs):
|
|
||||||
return Results()
|
|
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Dummy",
|
|
||||||
"module": "dummy",
|
|
||||||
"description": "I am dummy",
|
|
||||||
"author": "@balkian",
|
|
||||||
"version": "0.1"
|
|
||||||
}
|
|
7
tests/plugins/dummy_plugin/dummy.py
Normal file
7
tests/plugins/dummy_plugin/dummy.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from senpy.plugins import SentimentPlugin
|
||||||
|
|
||||||
|
|
||||||
|
class DummyPlugin(SentimentPlugin):
|
||||||
|
def analyse_entry(self, entry, params):
|
||||||
|
entry.text = entry.text[::-1]
|
||||||
|
yield entry
|
15
tests/plugins/dummy_plugin/dummy.senpy
Normal file
15
tests/plugins/dummy_plugin/dummy.senpy
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "Dummy",
|
||||||
|
"module": "dummy",
|
||||||
|
"description": "I am dummy",
|
||||||
|
"author": "@balkian",
|
||||||
|
"version": "0.1",
|
||||||
|
"extra_params": {
|
||||||
|
"example": {
|
||||||
|
"@id": "example_parameter",
|
||||||
|
"aliases": ["example", "ex"],
|
||||||
|
"required": false,
|
||||||
|
"default": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
tests/plugins/dummy_plugin/dummy_required.senpy
Normal file
14
tests/plugins/dummy_plugin/dummy_required.senpy
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"name": "DummyRequired",
|
||||||
|
"module": "dummy",
|
||||||
|
"description": "I am dummy",
|
||||||
|
"author": "@balkian",
|
||||||
|
"version": "0.1",
|
||||||
|
"extra_params": {
|
||||||
|
"example": {
|
||||||
|
"@id": "example_parameter",
|
||||||
|
"aliases": ["example", "ex"],
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,13 +1,11 @@
|
|||||||
from senpy.plugins import SenpyPlugin
|
from senpy.plugins import SenpyPlugin
|
||||||
from senpy.models import Results
|
|
||||||
from time import sleep
|
from time import sleep
|
||||||
|
|
||||||
|
|
||||||
class SleepPlugin(SenpyPlugin):
|
class SleepPlugin(SenpyPlugin):
|
||||||
|
|
||||||
def activate(self, *args, **kwargs):
|
def activate(self, *args, **kwargs):
|
||||||
sleep(self.timeout)
|
sleep(self.timeout)
|
||||||
|
|
||||||
def analyse(self, *args, **kwargs):
|
def analyse_entry(self, entry, params):
|
||||||
sleep(float(kwargs.get("timeout", self.timeout)))
|
sleep(float(params.get("timeout", self.timeout)))
|
||||||
return Results()
|
yield entry
|
@@ -2,9 +2,9 @@ import os
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from senpy.extensions import Senpy
|
from senpy.extensions import Senpy
|
||||||
|
from senpy import models
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
from flask.ext.testing import TestCase
|
from unittest import TestCase
|
||||||
from gevent import sleep
|
|
||||||
from itertools import product
|
from itertools import product
|
||||||
|
|
||||||
|
|
||||||
@@ -12,30 +12,39 @@ def check_dict(indic, template):
|
|||||||
return all(item in indic.items() for item in template.items())
|
return all(item in indic.items() for item in template.items())
|
||||||
|
|
||||||
|
|
||||||
class BlueprintsTest(TestCase):
|
def parse_resp(resp):
|
||||||
|
return models.from_json(resp.data.decode('utf-8'))
|
||||||
|
|
||||||
def create_app(self):
|
|
||||||
|
class BlueprintsTest(TestCase):
|
||||||
|
def setUp(self):
|
||||||
self.app = Flask("test_extensions")
|
self.app = Flask("test_extensions")
|
||||||
|
self.client = self.app.test_client()
|
||||||
self.senpy = Senpy()
|
self.senpy = Senpy()
|
||||||
self.senpy.init_app(self.app)
|
self.senpy.init_app(self.app)
|
||||||
self.dir = os.path.join(os.path.dirname(__file__), "..")
|
self.dir = os.path.join(os.path.dirname(__file__), "..")
|
||||||
self.senpy.add_folder(self.dir)
|
self.senpy.add_folder(self.dir)
|
||||||
self.senpy.activate_plugin("Dummy", sync=True)
|
self.senpy.activate_plugin("Dummy", sync=True)
|
||||||
return self.app
|
self.senpy.activate_plugin("DummyRequired", sync=True)
|
||||||
|
self.senpy.default_plugin = 'Dummy'
|
||||||
|
|
||||||
|
def assertCode(self, resp, code):
|
||||||
|
self.assertEqual(resp.status_code, code)
|
||||||
|
|
||||||
def test_home(self):
|
def test_home(self):
|
||||||
"""
|
"""
|
||||||
Calling with no arguments should ask the user for more arguments
|
Calling with no arguments should ask the user for more arguments
|
||||||
"""
|
"""
|
||||||
resp = self.client.get("/api/")
|
resp = self.client.get("/api/")
|
||||||
self.assert404(resp)
|
self.assertCode(resp, 400)
|
||||||
logging.debug(resp.json)
|
js = parse_resp(resp)
|
||||||
assert resp.json["status"] == 404
|
logging.debug(js)
|
||||||
|
assert js["status"] == 400
|
||||||
atleast = {
|
atleast = {
|
||||||
"status": 404,
|
"status": 400,
|
||||||
"message": "Missing or invalid parameters",
|
"message": "Missing or invalid parameters",
|
||||||
}
|
}
|
||||||
assert check_dict(resp.json, atleast)
|
assert check_dict(js, atleast)
|
||||||
|
|
||||||
def test_analysis(self):
|
def test_analysis(self):
|
||||||
"""
|
"""
|
||||||
@@ -43,81 +52,102 @@ class BlueprintsTest(TestCase):
|
|||||||
it should contain the context
|
it should contain the context
|
||||||
"""
|
"""
|
||||||
resp = self.client.get("/api/?i=My aloha mohame")
|
resp = self.client.get("/api/?i=My aloha mohame")
|
||||||
self.assert200(resp)
|
self.assertCode(resp, 200)
|
||||||
logging.debug("Got response: %s", resp.json)
|
js = parse_resp(resp)
|
||||||
assert "@context" in resp.json
|
logging.debug("Got response: %s", js)
|
||||||
assert "entries" in resp.json
|
assert "@context" in js
|
||||||
|
assert "entries" in js
|
||||||
|
|
||||||
|
def test_analysis_extra(self):
|
||||||
|
"""
|
||||||
|
Extra params that have a default should
|
||||||
|
"""
|
||||||
|
resp = self.client.get("/api/?i=My aloha mohame&algo=Dummy")
|
||||||
|
self.assertCode(resp, 200)
|
||||||
|
js = parse_resp(resp)
|
||||||
|
logging.debug("Got response: %s", js)
|
||||||
|
assert "@context" in js
|
||||||
|
assert "entries" in js
|
||||||
|
|
||||||
|
def test_analysis_extra_required(self):
|
||||||
|
"""
|
||||||
|
Extra params that have a required argument that does not
|
||||||
|
have a default should raise an error.
|
||||||
|
"""
|
||||||
|
resp = self.client.get("/api/?i=My aloha mohame&algo=DummyRequired")
|
||||||
|
self.assertCode(resp, 400)
|
||||||
|
js = parse_resp(resp)
|
||||||
|
logging.debug("Got response: %s", js)
|
||||||
|
assert isinstance(js, models.Error)
|
||||||
|
|
||||||
|
def test_error(self):
|
||||||
|
"""
|
||||||
|
The dummy plugin returns an empty response,\
|
||||||
|
it should contain the context
|
||||||
|
"""
|
||||||
|
resp = self.client.get("/api/?i=My aloha mohame&algo=DOESNOTEXIST")
|
||||||
|
self.assertCode(resp, 404)
|
||||||
|
js = parse_resp(resp)
|
||||||
|
logging.debug("Got response: %s", js)
|
||||||
|
assert isinstance(js, models.Error)
|
||||||
|
|
||||||
def test_list(self):
|
def test_list(self):
|
||||||
""" List the plugins """
|
""" List the plugins """
|
||||||
resp = self.client.get("/api/plugins/")
|
resp = self.client.get("/api/plugins/")
|
||||||
self.assert200(resp)
|
self.assertCode(resp, 200)
|
||||||
logging.debug(resp.json)
|
js = parse_resp(resp)
|
||||||
assert 'plugins' in resp.json
|
logging.debug(js)
|
||||||
plugins = resp.json['plugins']
|
assert 'plugins' in js
|
||||||
|
plugins = js['plugins']
|
||||||
assert len(plugins) > 1
|
assert len(plugins) > 1
|
||||||
assert list(p for p in plugins if p['name'] == "Dummy")
|
assert list(p for p in plugins if p['name'] == "Dummy")
|
||||||
assert "@context" in resp.json
|
assert "@context" in js
|
||||||
|
|
||||||
def test_headers(self):
|
def test_headers(self):
|
||||||
for i, j in product(["/api/plugins/?nothing=", "/api/?i=test&"],
|
for i, j in product(["/api/plugins/?nothing=", "/api/?i=test&"],
|
||||||
["inHeaders"]):
|
["inHeaders"]):
|
||||||
resp = self.client.get("%s" % (i))
|
resp = self.client.get("%s" % (i))
|
||||||
assert "@context" in resp.json
|
js = parse_resp(resp)
|
||||||
|
assert "@context" in js
|
||||||
resp = self.client.get("%s&%s=0" % (i, j))
|
resp = self.client.get("%s&%s=0" % (i, j))
|
||||||
assert "@context" in resp.json
|
js = parse_resp(resp)
|
||||||
|
assert "@context" in js
|
||||||
resp = self.client.get("%s&%s=1" % (i, j))
|
resp = self.client.get("%s&%s=1" % (i, j))
|
||||||
assert "@context" not in resp.json
|
js = parse_resp(resp)
|
||||||
|
assert "@context" not in js
|
||||||
resp = self.client.get("%s&%s=true" % (i, j))
|
resp = self.client.get("%s&%s=true" % (i, j))
|
||||||
assert "@context" not in resp.json
|
js = parse_resp(resp)
|
||||||
|
assert "@context" not in js
|
||||||
|
|
||||||
def test_detail(self):
|
def test_detail(self):
|
||||||
""" Show only one plugin"""
|
""" Show only one plugin"""
|
||||||
resp = self.client.get("/api/plugins/Dummy/")
|
resp = self.client.get("/api/plugins/Dummy/")
|
||||||
self.assert200(resp)
|
self.assertCode(resp, 200)
|
||||||
logging.debug(resp.json)
|
js = parse_resp(resp)
|
||||||
assert "@id" in resp.json
|
logging.debug(js)
|
||||||
assert resp.json["@id"] == "Dummy_0.1"
|
assert "@id" in js
|
||||||
|
assert js["@id"] == "plugins/Dummy_0.1"
|
||||||
def test_activate(self):
|
|
||||||
""" Activate and deactivate one plugin """
|
|
||||||
resp = self.client.get("/api/plugins/Dummy/deactivate")
|
|
||||||
self.assert200(resp)
|
|
||||||
sleep(0.5)
|
|
||||||
resp = self.client.get("/api/plugins/Dummy/")
|
|
||||||
self.assert200(resp)
|
|
||||||
assert "is_activated" in resp.json
|
|
||||||
assert resp.json["is_activated"] == False
|
|
||||||
resp = self.client.get("/api/plugins/Dummy/activate")
|
|
||||||
self.assert200(resp)
|
|
||||||
sleep(0.5)
|
|
||||||
resp = self.client.get("/api/plugins/Dummy/")
|
|
||||||
self.assert200(resp)
|
|
||||||
assert "is_activated" in resp.json
|
|
||||||
assert resp.json["is_activated"] == True
|
|
||||||
|
|
||||||
def test_default(self):
|
def test_default(self):
|
||||||
""" Show only one plugin"""
|
""" Show only one plugin"""
|
||||||
resp = self.client.get("/api/plugins/default/")
|
resp = self.client.get("/api/plugins/default/")
|
||||||
self.assert200(resp)
|
self.assertCode(resp, 200)
|
||||||
logging.debug(resp.json)
|
js = parse_resp(resp)
|
||||||
assert "@id" in resp.json
|
logging.debug(js)
|
||||||
assert resp.json["@id"] == "Dummy_0.1"
|
assert "@id" in js
|
||||||
resp = self.client.get("/api/plugins/Dummy/deactivate")
|
assert js["@id"] == "plugins/Dummy_0.1"
|
||||||
self.assert200(resp)
|
|
||||||
sleep(0.5)
|
|
||||||
resp = self.client.get("/api/plugins/default/")
|
|
||||||
self.assert404(resp)
|
|
||||||
|
|
||||||
def test_context(self):
|
def test_context(self):
|
||||||
resp = self.client.get("/api/contexts/context.jsonld")
|
resp = self.client.get("/api/contexts/context.jsonld")
|
||||||
self.assert200(resp)
|
self.assertCode(resp, 200)
|
||||||
assert "@context" in resp.json
|
js = parse_resp(resp)
|
||||||
|
assert "@context" in js
|
||||||
assert check_dict(
|
assert check_dict(
|
||||||
resp.json["@context"],
|
js["@context"],
|
||||||
{"marl": "http://www.gsi.dit.upm.es/ontologies/marl/ns#"})
|
{"marl": "http://www.gsi.dit.upm.es/ontologies/marl/ns#"})
|
||||||
|
|
||||||
def test_schema(self):
|
def test_schema(self):
|
||||||
resp = self.client.get("/api/schemas/definitions.json")
|
resp = self.client.get("/api/schemas/definitions.json")
|
||||||
self.assert200(resp)
|
self.assertCode(resp, 200)
|
||||||
assert "$schema" in resp.json
|
js = parse_resp(resp)
|
||||||
|
assert "$schema" in js
|
||||||
|
@@ -1,7 +1,11 @@
|
|||||||
import os
|
|
||||||
import logging
|
import logging
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
|
try:
|
||||||
|
from unittest.mock import patch
|
||||||
|
except ImportError:
|
||||||
|
from mock import patch
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
@@ -10,12 +14,14 @@ from senpy.models import Error
|
|||||||
|
|
||||||
|
|
||||||
class CLITest(TestCase):
|
class CLITest(TestCase):
|
||||||
|
|
||||||
def test_basic(self):
|
def test_basic(self):
|
||||||
self.assertRaises(Error, partial(main_function, []))
|
self.assertRaises(Error, partial(main_function, []))
|
||||||
res = main_function(['--input', 'test'])
|
|
||||||
assert 'entries' in res
|
with patch('senpy.extensions.Senpy.analyse') as patched:
|
||||||
res = main_function(['--input', 'test', '--algo', 'rand'])
|
main_function(['--input', 'test'])
|
||||||
assert 'entries' in res
|
|
||||||
assert 'analysis' in res
|
patched.assert_called_with(input='test')
|
||||||
assert res['analysis'][0]['name'] == 'rand'
|
with patch('senpy.extensions.Senpy.analyse') as patched:
|
||||||
|
main_function(['--input', 'test', '--algo', 'rand'])
|
||||||
|
|
||||||
|
patched.assert_called_with(input='test', algo='rand')
|
||||||
|
43
tests/test_client.py
Normal file
43
tests/test_client.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
from unittest import TestCase
|
||||||
|
try:
|
||||||
|
from unittest.mock import patch
|
||||||
|
except ImportError:
|
||||||
|
from mock import patch
|
||||||
|
|
||||||
|
from senpy.client import Client
|
||||||
|
from senpy.models import Results, Error
|
||||||
|
|
||||||
|
|
||||||
|
class Call(dict):
|
||||||
|
def __init__(self, obj):
|
||||||
|
self.obj = obj.jsonld()
|
||||||
|
self.status_code = 200
|
||||||
|
self.content = self.json()
|
||||||
|
|
||||||
|
def json(self):
|
||||||
|
return self.obj
|
||||||
|
|
||||||
|
|
||||||
|
class ModelsTest(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.host = '0.0.0.0'
|
||||||
|
self.port = 5000
|
||||||
|
|
||||||
|
def test_client(self):
|
||||||
|
endpoint = 'http://dummy/'
|
||||||
|
client = Client(endpoint)
|
||||||
|
success = Call(Results())
|
||||||
|
with patch('requests.request', return_value=success) as patched:
|
||||||
|
resp = client.analyse('hello')
|
||||||
|
assert isinstance(resp, Results)
|
||||||
|
patched.assert_called_with(
|
||||||
|
url=endpoint + '/', method='GET', params={'input': 'hello'})
|
||||||
|
error = Call(Error('Nothing'))
|
||||||
|
with patch('requests.request', return_value=error) as patched:
|
||||||
|
resp = client.analyse(input='hello', algorithm='NONEXISTENT')
|
||||||
|
assert isinstance(resp, Error)
|
||||||
|
patched.assert_called_with(
|
||||||
|
url=endpoint + '/',
|
||||||
|
method='GET',
|
||||||
|
params={'input': 'hello',
|
||||||
|
'algorithm': 'NONEXISTENT'})
|
@@ -2,22 +2,26 @@ from __future__ import print_function
|
|||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
try:
|
||||||
|
from unittest import mock
|
||||||
|
except ImportError:
|
||||||
|
import mock
|
||||||
|
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from senpy.extensions import Senpy
|
from senpy.extensions import Senpy
|
||||||
from senpy.models import Error
|
from senpy.models import Error
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
from flask.ext.testing import TestCase
|
from unittest import TestCase
|
||||||
|
|
||||||
|
|
||||||
class ExtensionsTest(TestCase):
|
class ExtensionsTest(TestCase):
|
||||||
|
def setUp(self):
|
||||||
def create_app(self):
|
self.app = Flask('test_extensions')
|
||||||
self.app = Flask("test_extensions")
|
|
||||||
self.dir = os.path.join(os.path.dirname(__file__))
|
self.dir = os.path.join(os.path.dirname(__file__))
|
||||||
self.senpy = Senpy(plugin_folder=self.dir, default_plugins=False)
|
self.senpy = Senpy(plugin_folder=self.dir,
|
||||||
self.senpy.init_app(self.app)
|
app=self.app,
|
||||||
|
default_plugins=False)
|
||||||
self.senpy.activate_plugin("Dummy", sync=True)
|
self.senpy.activate_plugin("Dummy", sync=True)
|
||||||
return self.app
|
|
||||||
|
|
||||||
def test_init(self):
|
def test_init(self):
|
||||||
""" Initialising the app with the extension. """
|
""" Initialising the app with the extension. """
|
||||||
@@ -34,9 +38,24 @@ class ExtensionsTest(TestCase):
|
|||||||
assert "Dummy" in self.senpy.plugins
|
assert "Dummy" in self.senpy.plugins
|
||||||
|
|
||||||
def test_enabling(self):
|
def test_enabling(self):
|
||||||
|
""" Enabling a plugin """
|
||||||
|
info = {
|
||||||
|
'name': 'TestPip',
|
||||||
|
'module': 'dummy',
|
||||||
|
'requirements': ['noop'],
|
||||||
|
'version': 0
|
||||||
|
}
|
||||||
|
root = os.path.join(self.dir, 'plugins', 'dummy_plugin')
|
||||||
|
name, module = self.senpy._load_plugin_from_info(info, root=root)
|
||||||
|
assert name == 'TestPip'
|
||||||
|
assert module
|
||||||
|
import noop
|
||||||
|
dir(noop)
|
||||||
|
|
||||||
|
def test_installing(self):
|
||||||
""" Enabling a plugin """
|
""" Enabling a plugin """
|
||||||
self.senpy.activate_all(sync=True)
|
self.senpy.activate_all(sync=True)
|
||||||
assert len(self.senpy.plugins) == 2
|
assert len(self.senpy.plugins) >= 3
|
||||||
assert self.senpy.plugins["Sleep"].is_activated
|
assert self.senpy.plugins["Sleep"].is_activated
|
||||||
|
|
||||||
def test_disabling(self):
|
def test_disabling(self):
|
||||||
@@ -57,6 +76,11 @@ class ExtensionsTest(TestCase):
|
|||||||
""" Don't analyse if there isn't any plugin installed """
|
""" Don't analyse if there isn't any plugin installed """
|
||||||
self.senpy.deactivate_all(sync=True)
|
self.senpy.deactivate_all(sync=True)
|
||||||
self.assertRaises(Error, partial(self.senpy.analyse, input="tupni"))
|
self.assertRaises(Error, partial(self.senpy.analyse, input="tupni"))
|
||||||
|
self.assertRaises(Error,
|
||||||
|
partial(
|
||||||
|
self.senpy.analyse,
|
||||||
|
input="tupni",
|
||||||
|
algorithm='Dummy'))
|
||||||
|
|
||||||
def test_analyse(self):
|
def test_analyse(self):
|
||||||
""" Using a plugin """
|
""" Using a plugin """
|
||||||
@@ -65,8 +89,23 @@ class ExtensionsTest(TestCase):
|
|||||||
r1 = self.senpy.analyse(
|
r1 = self.senpy.analyse(
|
||||||
algorithm="Dummy", input="tupni", output="tuptuo")
|
algorithm="Dummy", input="tupni", output="tuptuo")
|
||||||
r2 = self.senpy.analyse(input="tupni", output="tuptuo")
|
r2 = self.senpy.analyse(input="tupni", output="tuptuo")
|
||||||
assert r1.analysis[0].id[:5] == "Dummy"
|
assert r1.analysis[0] == "plugins/Dummy_0.1"
|
||||||
assert r2.analysis[0].id[:5] == "Dummy"
|
assert r2.analysis[0] == "plugins/Dummy_0.1"
|
||||||
|
assert r1.entries[0].text == 'input'
|
||||||
|
|
||||||
|
def test_analyse_error(self):
|
||||||
|
mm = mock.MagicMock()
|
||||||
|
mm.analyse_entry.side_effect = Error('error on analysis', status=900)
|
||||||
|
self.senpy.plugins['MOCK'] = mm
|
||||||
|
resp = self.senpy.analyse(input='nothing', algorithm='MOCK')
|
||||||
|
assert resp['message'] == 'error on analysis'
|
||||||
|
assert resp['status'] == 900
|
||||||
|
mm.analyse.side_effect = Exception('generic exception on analysis')
|
||||||
|
mm.analyse_entry.side_effect = Exception(
|
||||||
|
'generic exception on analysis')
|
||||||
|
resp = self.senpy.analyse(input='nothing', algorithm='MOCK')
|
||||||
|
assert resp['message'] == 'generic exception on analysis'
|
||||||
|
assert resp['status'] == 500
|
||||||
|
|
||||||
def test_filtering(self):
|
def test_filtering(self):
|
||||||
""" Filtering plugins """
|
""" Filtering plugins """
|
||||||
@@ -76,3 +115,7 @@ class ExtensionsTest(TestCase):
|
|||||||
self.senpy.deactivate_plugin("Dummy", sync=True)
|
self.senpy.deactivate_plugin("Dummy", sync=True)
|
||||||
assert not len(
|
assert not len(
|
||||||
self.senpy.filter_plugins(name="Dummy", is_activated=True))
|
self.senpy.filter_plugins(name="Dummy", is_activated=True))
|
||||||
|
|
||||||
|
def test_load_default_plugins(self):
|
||||||
|
senpy = Senpy(plugin_folder=self.dir, default_plugins=True)
|
||||||
|
assert len(senpy.plugins) > 1
|
||||||
|
@@ -1,26 +1,27 @@
|
|||||||
import os
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import jsonschema
|
import jsonschema
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import rdflib
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
from senpy.models import Response, Entry, Results, Sentiment, EmotionSet, Emotion, Error
|
from senpy.models import (Emotion,
|
||||||
|
EmotionAnalysis,
|
||||||
|
EmotionSet,
|
||||||
|
Entry,
|
||||||
|
Error,
|
||||||
|
Results,
|
||||||
|
Sentiment)
|
||||||
from senpy.plugins import SenpyPlugin
|
from senpy.plugins import SenpyPlugin
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
|
|
||||||
|
|
||||||
class ModelsTest(TestCase):
|
class ModelsTest(TestCase):
|
||||||
|
|
||||||
def test_jsonld(self):
|
def test_jsonld(self):
|
||||||
ctx = os.path.normpath(os.path.join(__file__, "..", "..", "..", "senpy", "schemas", "context.jsonld"))
|
prueba = {"id": "test", "analysis": [], "entries": []}
|
||||||
prueba = {"id": "test",
|
|
||||||
"analysis": [],
|
|
||||||
"entries": []}
|
|
||||||
r = Results(**prueba)
|
r = Results(**prueba)
|
||||||
print("Response's context: ")
|
print("Response's context: ")
|
||||||
pprint(r.context)
|
pprint(r._context)
|
||||||
|
|
||||||
assert r.id == "test"
|
assert r.id == "test"
|
||||||
|
|
||||||
@@ -34,10 +35,11 @@ class ModelsTest(TestCase):
|
|||||||
assert "id" not in j
|
assert "id" not in j
|
||||||
|
|
||||||
r6 = Results(**prueba)
|
r6 = Results(**prueba)
|
||||||
r6.entries.append(Entry({"@id":"ohno", "nif:isString":"Just testing"}))
|
e = Entry({"@id": "ohno", "nif:isString": "Just testing"})
|
||||||
|
r6.entries.append(e)
|
||||||
logging.debug("Reponse 6: %s", r6)
|
logging.debug("Reponse 6: %s", r6)
|
||||||
assert("marl" in r6.context)
|
assert ("marl" in r6._context)
|
||||||
assert("entries" in r6.context)
|
assert ("entries" in r6._context)
|
||||||
j6 = r6.jsonld(with_context=True)
|
j6 = r6.jsonld(with_context=True)
|
||||||
logging.debug("jsonld: %s", j6)
|
logging.debug("jsonld: %s", j6)
|
||||||
assert ("@context" in j6)
|
assert ("@context" in j6)
|
||||||
@@ -61,7 +63,6 @@ class ModelsTest(TestCase):
|
|||||||
assert j2['@id'] == 'test'
|
assert j2['@id'] == 'test'
|
||||||
assert 'id' not in j2
|
assert 'id' not in j2
|
||||||
|
|
||||||
|
|
||||||
def test_entries(self):
|
def test_entries(self):
|
||||||
e = Entry()
|
e = Entry()
|
||||||
self.assertRaises(jsonschema.ValidationError, e.validate)
|
self.assertRaises(jsonschema.ValidationError, e.validate)
|
||||||
@@ -103,5 +104,46 @@ class ModelsTest(TestCase):
|
|||||||
logging.debug(c)
|
logging.debug(c)
|
||||||
p.validate()
|
p.validate()
|
||||||
|
|
||||||
def test_frame_response(self):
|
def test_str(self):
|
||||||
|
"""The string representation shouldn't include private variables"""
|
||||||
|
r = Results()
|
||||||
|
p = SenpyPlugin({"name": "STR test", "version": 0})
|
||||||
|
p._testing = 0
|
||||||
|
s = str(p)
|
||||||
|
assert "_testing" not in s
|
||||||
|
r.analysis.append(p)
|
||||||
|
s = str(r)
|
||||||
|
assert "_testing" not in s
|
||||||
|
|
||||||
|
def test_turtle(self):
|
||||||
|
"""Any model should be serializable as a turtle file"""
|
||||||
|
ana = EmotionAnalysis()
|
||||||
|
res = Results()
|
||||||
|
res.analysis.append(ana)
|
||||||
|
entry = Entry(text='Just testing')
|
||||||
|
eSet = EmotionSet()
|
||||||
|
emotion = Emotion()
|
||||||
|
entry.emotions.append(eSet)
|
||||||
|
res.entries.append(entry)
|
||||||
|
eSet.onyx__hasEmotion.append(emotion)
|
||||||
|
eSet.prov__wasGeneratedBy = ana.id
|
||||||
|
triples = ('ana a :Analysis',
|
||||||
|
'entry a :entry',
|
||||||
|
' nif:isString "Just testing"',
|
||||||
|
' onyx:hasEmotionSet eSet',
|
||||||
|
'eSet a onyx:EmotionSet',
|
||||||
|
' prov:wasGeneratedBy ana',
|
||||||
|
' onyx:hasEmotion emotion',
|
||||||
|
'emotion a onyx:Emotion',
|
||||||
|
'res a :results',
|
||||||
|
' me:AnalysisInvoloved ana',
|
||||||
|
' prov:used entry')
|
||||||
|
|
||||||
|
t = res.serialize(format='turtle')
|
||||||
|
print(t)
|
||||||
|
g = rdflib.Graph().parse(data=t, format='turtle')
|
||||||
|
assert len(g) == len(triples)
|
||||||
|
|
||||||
|
def test_convert_emotions(self):
|
||||||
|
"""It should be possible to convert between different emotion models"""
|
||||||
pass
|
pass
|
||||||
|
@@ -1,31 +1,34 @@
|
|||||||
#!/bin/env python
|
#!/bin/env python
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import logging
|
|
||||||
import pickle
|
import pickle
|
||||||
import shutil
|
import shutil
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
from senpy.models import Results, Entry
|
from senpy.models import Results, Entry
|
||||||
from senpy.plugins import SenpyPlugin, ShelfMixin
|
from senpy.plugins import SentimentPlugin, ShelfMixin
|
||||||
|
|
||||||
|
|
||||||
class ShelfTest(ShelfMixin, SenpyPlugin):
|
class ShelfDummyPlugin(SentimentPlugin, ShelfMixin):
|
||||||
|
def activate(self, *args, **kwargs):
|
||||||
|
if 'counter' not in self.sh:
|
||||||
|
self.sh['counter'] = 0
|
||||||
|
self.save()
|
||||||
|
|
||||||
def test(self, key=None, value=None):
|
def deactivate(self, *args, **kwargs):
|
||||||
assert key in self.sh
|
self.save()
|
||||||
print('Checking: sh[{}] == {}'.format(key, value))
|
|
||||||
print('SH[{}]: {}'.format(key, self.sh[key]))
|
def analyse(self, *args, **kwargs):
|
||||||
assert self.sh[key] == value
|
self.sh['counter'] = self.sh['counter'] + 1
|
||||||
|
e = Entry()
|
||||||
|
e.nif__isString = self.sh['counter']
|
||||||
|
r = Results()
|
||||||
class ModelsTest(TestCase):
|
r.entries.append(e)
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
class PluginsTest(TestCase):
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
if os.path.exists(self.shelf_dir):
|
if os.path.exists(self.shelf_dir):
|
||||||
shutil.rmtree(self.shelf_dir)
|
shutil.rmtree(self.shelf_dir)
|
||||||
@@ -37,16 +40,28 @@ class ModelsTest(TestCase):
|
|||||||
self.shelf_dir = tempfile.mkdtemp()
|
self.shelf_dir = tempfile.mkdtemp()
|
||||||
self.shelf_file = os.path.join(self.shelf_dir, "shelf")
|
self.shelf_file = os.path.join(self.shelf_dir, "shelf")
|
||||||
|
|
||||||
|
def test_shelf_file(self):
|
||||||
|
a = ShelfDummyPlugin(
|
||||||
|
info={'name': 'default_shelve_file',
|
||||||
|
'version': 'test'})
|
||||||
|
a.activate()
|
||||||
|
assert os.path.isfile(a.shelf_file)
|
||||||
|
os.remove(a.shelf_file)
|
||||||
|
|
||||||
def test_shelf(self):
|
def test_shelf(self):
|
||||||
''' A shelf is created and the value is stored '''
|
''' A shelf is created and the value is stored '''
|
||||||
a = ShelfTest(info={'name': 'shelve',
|
a = ShelfDummyPlugin(info={
|
||||||
|
'name': 'shelve',
|
||||||
'version': 'test',
|
'version': 'test',
|
||||||
'shelf_file': self.shelf_file})
|
'shelf_file': self.shelf_file
|
||||||
|
})
|
||||||
assert a.sh == {}
|
assert a.sh == {}
|
||||||
|
a.activate()
|
||||||
|
assert a.sh == {'counter': 0}
|
||||||
assert a.shelf_file == self.shelf_file
|
assert a.shelf_file == self.shelf_file
|
||||||
|
|
||||||
a.sh['a'] = 'fromA'
|
a.sh['a'] = 'fromA'
|
||||||
a.test(key='a', value='fromA')
|
assert a.sh['a'] == 'fromA'
|
||||||
|
|
||||||
a.save()
|
a.save()
|
||||||
|
|
||||||
@@ -54,19 +69,54 @@ class ModelsTest(TestCase):
|
|||||||
|
|
||||||
assert sh['a'] == 'fromA'
|
assert sh['a'] == 'fromA'
|
||||||
|
|
||||||
|
def test_dummy_shelf(self):
|
||||||
|
a = ShelfDummyPlugin(info={
|
||||||
|
'name': 'DummyShelf',
|
||||||
|
'shelf_file': self.shelf_file,
|
||||||
|
'version': 'test'
|
||||||
|
})
|
||||||
|
a.activate()
|
||||||
|
|
||||||
|
assert a.shelf_file == self.shelf_file
|
||||||
|
res1 = a.analyse(input=1)
|
||||||
|
assert res1.entries[0].nif__isString == 1
|
||||||
|
res2 = a.analyse(input=1)
|
||||||
|
assert res2.entries[0].nif__isString == 2
|
||||||
|
|
||||||
def test_two(self):
|
def test_two(self):
|
||||||
''' Reusing the values of a previous shelf '''
|
''' Reusing the values of a previous shelf '''
|
||||||
a = ShelfTest(info={'name': 'shelve',
|
a = ShelfDummyPlugin(info={
|
||||||
|
'name': 'shelve',
|
||||||
'version': 'test',
|
'version': 'test',
|
||||||
'shelf_file': self.shelf_file})
|
'shelf_file': self.shelf_file
|
||||||
|
})
|
||||||
|
a.activate()
|
||||||
print('Shelf file: %s' % a.shelf_file)
|
print('Shelf file: %s' % a.shelf_file)
|
||||||
a.sh['a'] = 'fromA'
|
a.sh['a'] = 'fromA'
|
||||||
a.save()
|
a.save()
|
||||||
|
|
||||||
b = ShelfTest(info={'name': 'shelve',
|
b = ShelfDummyPlugin(info={
|
||||||
|
'name': 'shelve',
|
||||||
'version': 'test',
|
'version': 'test',
|
||||||
'shelf_file': self.shelf_file})
|
'shelf_file': self.shelf_file
|
||||||
b.test(key='a', value='fromA')
|
})
|
||||||
|
b.activate()
|
||||||
|
assert b.sh['a'] == 'fromA'
|
||||||
b.sh['a'] = 'fromB'
|
b.sh['a'] = 'fromB'
|
||||||
assert b.sh['a'] == 'fromB'
|
assert b.sh['a'] == 'fromB'
|
||||||
|
|
||||||
|
def test_extra_params(self):
|
||||||
|
''' Should be able to set extra parameters'''
|
||||||
|
a = ShelfDummyPlugin(info={
|
||||||
|
'name': 'shelve',
|
||||||
|
'version': 'test',
|
||||||
|
'shelf_file': self.shelf_file,
|
||||||
|
'extra_params': {
|
||||||
|
'example': {
|
||||||
|
'aliases': ['example', 'ex'],
|
||||||
|
'required': True,
|
||||||
|
'default': 'nonsense'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
assert 'example' in a.extra_params
|
||||||
|
60
tests/test_schemas.py
Normal file
60
tests/test_schemas.py
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import json
|
||||||
|
import unittest
|
||||||
|
import os
|
||||||
|
from os import path
|
||||||
|
from fnmatch import fnmatch
|
||||||
|
|
||||||
|
from jsonschema import RefResolver, Draft4Validator, ValidationError
|
||||||
|
|
||||||
|
root_path = path.join(path.dirname(path.realpath(__file__)), '..')
|
||||||
|
schema_folder = path.join(root_path, 'senpy', 'schemas')
|
||||||
|
examples_path = path.join(root_path, 'docs', 'examples')
|
||||||
|
bad_examples_path = path.join(root_path, 'docs', 'bad-examples')
|
||||||
|
|
||||||
|
|
||||||
|
class JSONSchemaTests(unittest.TestCase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def do_create_(jsfile, success):
|
||||||
|
def do_expected(self):
|
||||||
|
with open(jsfile) as f:
|
||||||
|
js = json.load(f)
|
||||||
|
try:
|
||||||
|
assert '@type' in js
|
||||||
|
schema_name = js['@type']
|
||||||
|
with open(os.path.join(schema_folder, schema_name +
|
||||||
|
".json")) as file_object:
|
||||||
|
schema = json.load(file_object)
|
||||||
|
resolver = RefResolver('file://' + schema_folder + '/', schema)
|
||||||
|
validator = Draft4Validator(schema, resolver=resolver)
|
||||||
|
validator.validate(js)
|
||||||
|
except (AssertionError, ValidationError, KeyError) as ex:
|
||||||
|
if success:
|
||||||
|
raise
|
||||||
|
|
||||||
|
return do_expected
|
||||||
|
|
||||||
|
|
||||||
|
def add_examples(dirname, success):
|
||||||
|
for dirpath, dirnames, filenames in os.walk(dirname):
|
||||||
|
for i in filenames:
|
||||||
|
if fnmatch(i, '*.json'):
|
||||||
|
filename = path.join(dirpath, i)
|
||||||
|
test_method = do_create_(filename, success)
|
||||||
|
test_method.__name__ = 'test_file_%s_success_%s' % (filename,
|
||||||
|
success)
|
||||||
|
test_method.__doc__ = '%s should %svalidate' % (filename, ''
|
||||||
|
if success else
|
||||||
|
'not')
|
||||||
|
setattr(JSONSchemaTests, test_method.__name__, test_method)
|
||||||
|
del test_method
|
||||||
|
|
||||||
|
|
||||||
|
add_examples(examples_path, True)
|
||||||
|
add_examples(bad_examples_path, False)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
2
version.sh
Executable file
2
version.sh
Executable file
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
git describe --long --tags --dirty
|
Reference in New Issue
Block a user