mirror of
https://github.com/gsi-upm/senpy
synced 2025-10-20 02:08:26 +00:00
Compare commits
41 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
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 | ||
|
5d5de0bc50 | ||
|
0454fb1afe | ||
|
5e36c71fa7 | ||
|
c8e742f96e | ||
|
1e7ae13700 | ||
|
bf30c04a52 |
73
.gitlab-ci.yml
Normal file
73
.gitlab-ci.yml
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
image: docker:latest
|
||||||
|
|
||||||
|
|
||||||
|
# When using dind, it's wise to use the overlayfs driver for
|
||||||
|
# improved performance.
|
||||||
|
variables:
|
||||||
|
DOCKER_DRIVER: overlay
|
||||||
|
DOCKERFILE: Dockerfile
|
||||||
|
|
||||||
|
stages:
|
||||||
|
- test
|
||||||
|
- images
|
||||||
|
- release
|
||||||
|
|
||||||
|
.test: &test_definition
|
||||||
|
variables:
|
||||||
|
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.eggs"
|
||||||
|
cache:
|
||||||
|
paths:
|
||||||
|
- .eggs/
|
||||||
|
key: "$CI_PROJECT_NAME"
|
||||||
|
stage: test
|
||||||
|
script:
|
||||||
|
- python setup.py test
|
||||||
|
|
||||||
|
test-3.5:
|
||||||
|
<<: *test_definition
|
||||||
|
image: "python:3.5"
|
||||||
|
|
||||||
|
test-3.4:
|
||||||
|
<<: *test_definition
|
||||||
|
image: "python:3.4"
|
||||||
|
|
||||||
|
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
|
||||||
|
33
Dockerfile
33
Dockerfile
@@ -1,33 +0,0 @@
|
|||||||
from python:2.7
|
|
||||||
|
|
||||||
RUN apt-get update
|
|
||||||
RUN apt-get -y install git
|
|
||||||
RUN mkdir -p /senpy-plugins
|
|
||||||
|
|
||||||
RUN apt-get -y install python-numpy
|
|
||||||
RUN apt-get -y install python-scipy
|
|
||||||
RUN apt-get -y install python-sklearn
|
|
||||||
RUN apt-get -y install python-gevent
|
|
||||||
RUN apt-get -y install libopenblas-dev
|
|
||||||
RUN apt-get -y install gfortran
|
|
||||||
RUN apt-get -y install libxml2-dev libxslt1-dev python-dev
|
|
||||||
|
|
||||||
#RUN pip install --upgrade pip
|
|
||||||
|
|
||||||
ADD id_rsa /root/.ssh/id_rsa
|
|
||||||
RUN chmod 700 /root/.ssh/id_rsa
|
|
||||||
RUN echo "Host github.com\n\tStrictHostKeyChecking no\n" >> /root/.ssh/config
|
|
||||||
|
|
||||||
RUN git clone https://github.com/gsi-upm/senpy /usr/src/app/
|
|
||||||
RUN git clone git@github.com:gsi-upm/senpy-plugins-enterprise /senpy-plugins/enterprise
|
|
||||||
RUN git clone https://github.com/gsi-upm/senpy-plugins-community /senpy-plugins/community
|
|
||||||
|
|
||||||
RUN pip install /usr/src/app
|
|
||||||
RUN pip install --no-use-wheel -r /senpy-plugins/enterprise/requirements.txt
|
|
||||||
RUN python -m nltk.downloader stopwords
|
|
||||||
RUN python -m nltk.downloader punkt
|
|
||||||
RUN python -m nltk.downloader maxent_treebank_pos_tagger
|
|
||||||
RUN python -m nltk.downloader wordnet
|
|
||||||
|
|
||||||
WORKDIR /senpy-plugins
|
|
||||||
ENTRYPOINT ["python", "-m", "senpy", "-f", ".", "--host", "0.0.0.0"]
|
|
1
Dockerfile
Symbolic link
1
Dockerfile
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
Dockerfile-3.5
|
9
Dockerfile-2.7
Normal file
9
Dockerfile-2.7
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from python:2.7
|
||||||
|
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
ADD requirements.txt /usr/src/app/
|
||||||
|
RUN pip install -r requirements.txt
|
||||||
|
ADD . /usr/src/app/
|
||||||
|
RUN pip install .
|
||||||
|
|
||||||
|
ENTRYPOINT ["python", "-m", "senpy", "-f", ".", "--host", "0.0.0.0"]
|
9
Dockerfile-3.4
Normal file
9
Dockerfile-3.4
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from python:3.4
|
||||||
|
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
ADD requirements.txt /usr/src/app/
|
||||||
|
RUN pip install -r requirements.txt
|
||||||
|
ADD . /usr/src/app/
|
||||||
|
RUN pip install .
|
||||||
|
|
||||||
|
ENTRYPOINT ["python", "-m", "senpy", "-f", ".", "--host", "0.0.0.0"]
|
9
Dockerfile-3.5
Normal file
9
Dockerfile-3.5
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from python:3.5
|
||||||
|
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
ADD requirements.txt /usr/src/app/
|
||||||
|
RUN pip install -r requirements.txt
|
||||||
|
ADD . /usr/src/app/
|
||||||
|
RUN pip install .
|
||||||
|
|
||||||
|
ENTRYPOINT ["python", "-m", "senpy", "-f", ".", "--host", "0.0.0.0"]
|
33
Dockerfile.deps
Normal file
33
Dockerfile.deps
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
from python:2.7
|
||||||
|
|
||||||
|
RUN apt-get update
|
||||||
|
RUN apt-get -y install git
|
||||||
|
RUN mkdir -p /senpy-plugins
|
||||||
|
|
||||||
|
RUN apt-get -y install python-numpy
|
||||||
|
RUN apt-get -y install python-scipy
|
||||||
|
RUN apt-get -y install python-sklearn
|
||||||
|
RUN apt-get -y install python-gevent
|
||||||
|
RUN apt-get -y install libopenblas-dev
|
||||||
|
RUN apt-get -y install gfortran
|
||||||
|
RUN apt-get -y install libxml2-dev libxslt1-dev python-dev
|
||||||
|
|
||||||
|
#RUN pip install --upgrade pip
|
||||||
|
|
||||||
|
ADD id_rsa /root/.ssh/id_rsa
|
||||||
|
RUN chmod 700 /root/.ssh/id_rsa
|
||||||
|
RUN echo "Host github.com\n\tStrictHostKeyChecking no\n" >> /root/.ssh/config
|
||||||
|
|
||||||
|
RUN git clone https://github.com/gsi-upm/senpy /usr/src/app/
|
||||||
|
RUN git clone git@github.com:gsi-upm/senpy-plugins-enterprise /senpy-plugins/enterprise
|
||||||
|
RUN git clone https://github.com/gsi-upm/senpy-plugins-community /senpy-plugins/community
|
||||||
|
|
||||||
|
RUN pip install /usr/src/app
|
||||||
|
RUN pip install --no-use-wheel -r /senpy-plugins/enterprise/requirements.txt
|
||||||
|
RUN python -m nltk.downloader stopwords
|
||||||
|
RUN python -m nltk.downloader punkt
|
||||||
|
RUN python -m nltk.downloader maxent_treebank_pos_tagger
|
||||||
|
RUN python -m nltk.downloader wordnet
|
||||||
|
|
||||||
|
WORKDIR /senpy-plugins
|
||||||
|
ENTRYPOINT ["python", "-m", "senpy", "-f", ".", "--host", "0.0.0.0"]
|
9
Dockerfile.template
Normal file
9
Dockerfile.template
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from python:{{PYVERSION}}
|
||||||
|
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
ADD requirements.txt /usr/src/app/
|
||||||
|
RUN pip install -r requirements.txt
|
||||||
|
ADD . /usr/src/app/
|
||||||
|
RUN pip install .
|
||||||
|
|
||||||
|
ENTRYPOINT ["python", "-m", "senpy", "-f", ".", "--host", "0.0.0.0"]
|
@@ -1,7 +1,7 @@
|
|||||||
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
|
||||||
graft senpy/plugins
|
graft senpy/plugins
|
||||||
graft senpy/schemas
|
graft senpy/schemas
|
||||||
graft senpy/templates
|
graft senpy/templates
|
||||||
|
83
Makefile
Normal file
83
Makefile
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
PYVERSIONS=3.5 3.4 2.7
|
||||||
|
PYMAIN=$(firstword $(PYVERSIONS))
|
||||||
|
NAME=senpy
|
||||||
|
REPO=gsiupm
|
||||||
|
VERSION=$(shell cat $(NAME)/VERSION)
|
||||||
|
TARNAME=$(NAME)-$(subst -,.,$(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
|
||||||
|
|
||||||
|
yapf:
|
||||||
|
yapf -i -r senpy
|
||||||
|
yapf -i -r tests
|
||||||
|
|
||||||
|
dev:
|
||||||
|
pip install --user pre-commit
|
||||||
|
pre-commit install
|
||||||
|
|
||||||
|
dockerfiles: $(addprefix Dockerfile-,$(PYVERSIONS))
|
||||||
|
@unlink Dockerfile >/dev/null
|
||||||
|
ln -s Dockerfile-$(PYMAIN) Dockerfile
|
||||||
|
|
||||||
|
Dockerfile-%: Dockerfile.template
|
||||||
|
sed "s/{{PYVERSION}}/$*/" Dockerfile.template > Dockerfile-$*
|
||||||
|
|
||||||
|
quick_build: $(addprefix build-, $(PYMAIN))
|
||||||
|
|
||||||
|
build: $(addprefix build-, $(PYVERSIONS))
|
||||||
|
|
||||||
|
build-%: Dockerfile-%
|
||||||
|
docker build -t '$(IMAGENAME)-python$*' -f Dockerfile-$* .;
|
||||||
|
|
||||||
|
quick_test: $(addprefix test-,$(PYMAIN))
|
||||||
|
|
||||||
|
test: $(addprefix test-,$(PYVERSIONS))
|
||||||
|
|
||||||
|
debug-%:
|
||||||
|
(docker start $(NAME)-debug && docker attach $(NAME)-debug) || docker run -w /usr/src/app/ -v $$PWD:/usr/src/app --entrypoint=/bin/bash -ti --name $(NAME)-debug '$(IMAGENAME)-python$*'
|
||||||
|
|
||||||
|
debug: debug-$(PYMAIN)
|
||||||
|
|
||||||
|
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[1] != "${VERSION}"){ print $$1;}}' | xargs docker rm 2>/dev/null|| true
|
||||||
|
@docker images | awk '/$(REPO)\/$(NAME)/{ split($$2, vers, "-"); if(vers[1] != "${VERSION}"){ print $$1":"$$2;}}' | xargs docker rmi 2>/dev/null|| true
|
||||||
|
@docker rmi $(NAME)-debug 2>/dev/null || true
|
||||||
|
|
||||||
|
upload_git:
|
||||||
|
git commit -a
|
||||||
|
git tag ${VERSION}
|
||||||
|
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)'
|
||||||
|
|
||||||
|
.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
|
||||||
------------
|
------------
|
||||||
@@ -30,7 +30,7 @@ Alternatively, you can use the development version:
|
|||||||
|
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
git clone git@github.com:gsi-upm/senpy
|
git clone http://github.com/gsi-upm/senpy
|
||||||
cd senpy
|
cd senpy
|
||||||
pip install --user .
|
pip install --user .
|
||||||
|
|
||||||
@@ -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",
|
@@ -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",
|
@@ -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``
|
||||||
|
@@ -2,6 +2,8 @@ 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.
|
||||||
|
|
||||||
@@ -34,7 +36,7 @@ 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 Results.
|
* analyse: called in every user requests. It takes in the parameters supplied by a user and should return a senpy Response.
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
|
@@ -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
|
||||||
---------------------
|
---------------------
|
||||||
|
@@ -4,8 +4,9 @@ requests>=2.4.1
|
|||||||
GitPython>=0.3.2.RC1
|
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
|
||||||
|
semver
|
||||||
|
1
senpy/VERSION
Normal file
1
senpy/VERSION
Normal file
@@ -0,0 +1 @@
|
|||||||
|
0.7.1
|
@@ -17,5 +17,24 @@
|
|||||||
"""
|
"""
|
||||||
Sentiment analysis server in Python
|
Sentiment analysis server in Python
|
||||||
"""
|
"""
|
||||||
|
from __future__ import print_function
|
||||||
|
from .version import __version__
|
||||||
|
|
||||||
__version__ = "0.5.5"
|
try:
|
||||||
|
import semver
|
||||||
|
__version_info__ = semver.parse_version_info(__version__)
|
||||||
|
|
||||||
|
if __version_info__.prerelease:
|
||||||
|
import logging
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
msg = 'WARNING: You are using a pre-release version of {} ({})'.format(
|
||||||
|
__name__, __version__)
|
||||||
|
if len(logging.root.handlers) > 0:
|
||||||
|
logger.info(msg)
|
||||||
|
else:
|
||||||
|
import sys
|
||||||
|
print(msg, file=sys.stderr)
|
||||||
|
except ImportError:
|
||||||
|
print('semver not installed. Not doing version checking')
|
||||||
|
|
||||||
|
__all__ = ['api', 'blueprints', 'cli', 'extensions', 'models', 'plugins']
|
||||||
|
@@ -24,7 +24,6 @@ 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 argparse
|
import argparse
|
||||||
@@ -34,37 +33,51 @@ patch_all(thread=False)
|
|||||||
|
|
||||||
SERVER_PORT = os.environ.get("PORT", 5000)
|
SERVER_PORT = os.environ.get("PORT", 5000)
|
||||||
|
|
||||||
|
|
||||||
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(
|
||||||
'-l',
|
'--level',
|
||||||
metavar='logging_level',
|
'-l',
|
||||||
type=str,
|
metavar='logging_level',
|
||||||
default="INFO",
|
type=str,
|
||||||
help='Logging level')
|
default="INFO",
|
||||||
parser.add_argument('--debug',
|
help='Logging level')
|
||||||
'-d',
|
parser.add_argument(
|
||||||
action='store_true',
|
'--debug',
|
||||||
default=False,
|
'-d',
|
||||||
help='Run the application in debug mode')
|
action='store_true',
|
||||||
parser.add_argument('--default-plugins',
|
default=False,
|
||||||
action='store_true',
|
help='Run the application in debug mode')
|
||||||
default=False,
|
parser.add_argument(
|
||||||
help='Load the default plugins')
|
'--default-plugins',
|
||||||
parser.add_argument('--host',
|
action='store_true',
|
||||||
type=str,
|
default=False,
|
||||||
default="127.0.0.1",
|
help='Load the default plugins')
|
||||||
help='Use 0.0.0.0 to accept requests from any host.')
|
parser.add_argument(
|
||||||
parser.add_argument('--port',
|
'--host',
|
||||||
'-p',
|
type=str,
|
||||||
type=int,
|
default="127.0.0.1",
|
||||||
default=SERVER_PORT,
|
help='Use 0.0.0.0 to accept requests from any host.')
|
||||||
help='Port to listen on.')
|
parser.add_argument(
|
||||||
parser.add_argument('--plugins-folder',
|
'--port',
|
||||||
'-f',
|
'-p',
|
||||||
type=str,
|
type=int,
|
||||||
default='plugins',
|
default=SERVER_PORT,
|
||||||
help='Where to look for plugins.')
|
help='Port to listen on.')
|
||||||
|
parser.add_argument(
|
||||||
|
'--plugins-folder',
|
||||||
|
'-f',
|
||||||
|
type=str,
|
||||||
|
default='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()
|
||||||
@@ -72,6 +85,9 @@ def main():
|
|||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.debug = args.debug
|
app.debug = args.debug
|
||||||
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 +96,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()
|
||||||
|
16
senpy/api.py
16
senpy/api.py
@@ -1,9 +1,8 @@
|
|||||||
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"],
|
||||||
@@ -25,7 +24,7 @@ CLI_PARAMS = {
|
|||||||
"required": True,
|
"required": True,
|
||||||
"default": "."
|
"default": "."
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
NIF_PARAMS = {
|
NIF_PARAMS = {
|
||||||
"input": {
|
"input": {
|
||||||
@@ -96,10 +95,11 @@ 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,
|
message = Error(
|
||||||
message="Missing or invalid parameters",
|
status=404,
|
||||||
parameters=outdict,
|
message="Missing or invalid parameters",
|
||||||
errors={param: error for param, error in
|
parameters=outdict,
|
||||||
iteritems(wrong_params)})
|
errors={param: error
|
||||||
|
for param, error in iteritems(wrong_params)})
|
||||||
raise message
|
raise message
|
||||||
return outdict
|
return outdict
|
||||||
|
@@ -17,12 +17,12 @@
|
|||||||
"""
|
"""
|
||||||
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, parse_params
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
import json
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -30,6 +30,7 @@ logger = logging.getLogger(__name__)
|
|||||||
api_blueprint = Blueprint("api", __name__)
|
api_blueprint = Blueprint("api", __name__)
|
||||||
demo_blueprint = Blueprint("demo", __name__)
|
demo_blueprint = Blueprint("demo", __name__)
|
||||||
|
|
||||||
|
|
||||||
def get_params(req):
|
def get_params(req):
|
||||||
if req.method == 'POST':
|
if req.method == 'POST':
|
||||||
indict = req.form.to_dict(flat=True)
|
indict = req.form.to_dict(flat=True)
|
||||||
@@ -44,17 +45,20 @@ def get_params(req):
|
|||||||
def index():
|
def index():
|
||||||
return render_template("index.html")
|
return render_template("index.html")
|
||||||
|
|
||||||
|
|
||||||
@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})
|
return jsonify({"@context": Response.context})
|
||||||
|
|
||||||
|
|
||||||
@api_blueprint.route('/schemas/<schema>')
|
@api_blueprint.route('/schemas/<schema>')
|
||||||
def schema(schema="definitions"):
|
def schema(schema="definitions"):
|
||||||
try:
|
try:
|
||||||
return jsonify(read_schema(schema))
|
return jsonify(read_schema(schema))
|
||||||
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):
|
||||||
@@ -73,12 +77,15 @@ def basic_api(f):
|
|||||||
response = ex
|
response = ex
|
||||||
in_headers = web_params["inHeaders"] != "0"
|
in_headers = web_params["inHeaders"] != "0"
|
||||||
headers = {'X-ORIGINAL-PARAMS': raw_params}
|
headers = {'X-ORIGINAL-PARAMS': raw_params}
|
||||||
return response.flask(in_headers=in_headers,
|
return response.flask(
|
||||||
headers=headers,
|
in_headers=in_headers,
|
||||||
context_uri=url_for('api.context', entity=type(response).__name__,
|
headers=headers,
|
||||||
_external=True))
|
context_uri=url_for(
|
||||||
|
'api.context', entity=type(response).__name__, _external=True))
|
||||||
|
|
||||||
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,13 +100,12 @@ 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'])
|
@api_blueprint.route('/plugins/<plugin>/<action>', methods=['POST', 'GET'])
|
||||||
@basic_api
|
@basic_api
|
||||||
def plugin(plugin=None, action="list"):
|
def plugin(plugin=None, action="list"):
|
||||||
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
|
response = sp.default_plugin
|
||||||
plugin = response.name
|
plugin = response.name
|
||||||
@@ -110,15 +116,8 @@ def plugin(plugin=None, action="list"):
|
|||||||
if action == "list":
|
if action == "list":
|
||||||
return response
|
return response
|
||||||
method = "{}_plugin".format(action)
|
method = "{}_plugin".format(action)
|
||||||
if(hasattr(sp, method)):
|
if (hasattr(sp, method)):
|
||||||
getattr(sp, method)(plugin)
|
getattr(sp, method)(plugin)
|
||||||
return Response(message="Ok")
|
return Response(message="Ok")
|
||||||
else:
|
else:
|
||||||
return Error(message="action '{}' not allowed".format(action))
|
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'}
|
||||||
'''
|
'''
|
||||||
@@ -11,13 +12,14 @@ def argv_to_dict(argv):
|
|||||||
for i in range(len(argv)):
|
for i in range(len(argv)):
|
||||||
if argv[i][0] == '-':
|
if argv[i][0] == '-':
|
||||||
key = argv[i].strip('-')
|
key = argv[i].strip('-')
|
||||||
value = argv[i+1] if len(argv)>i+1 else None
|
value = argv[i + 1] if len(argv) > i + 1 else None
|
||||||
if value and value[0] == '-':
|
if value and value[0] == '-':
|
||||||
cli_dict[key] = ""
|
cli_dict[key] = ""
|
||||||
else:
|
else:
|
||||||
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()
|
||||||
|
|
||||||
|
45
senpy/client.py
Normal file
45
senpy/client.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
c = Client('http://senpy.cluster.gsi.dit.upm.es/api/')
|
||||||
|
resp = c.analyse('hello')
|
||||||
|
# print(resp)
|
||||||
|
print(resp.entries)
|
||||||
|
resp.validate()
|
@@ -2,17 +2,15 @@
|
|||||||
"""
|
"""
|
||||||
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
|
||||||
from .models import Error
|
from .models import Error
|
||||||
from .blueprints import api_blueprint, demo_blueprint
|
from .blueprints import api_blueprint, demo_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 git import Repo, InvalidGitRepositoryError
|
||||||
from functools import partial
|
|
||||||
|
from threading import Thread
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import fnmatch
|
import fnmatch
|
||||||
@@ -21,17 +19,19 @@ import sys
|
|||||||
import imp
|
import imp
|
||||||
import logging
|
import logging
|
||||||
import traceback
|
import traceback
|
||||||
import gevent
|
import yaml
|
||||||
import json
|
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="plugins",
|
||||||
|
default_plugins=False):
|
||||||
self.app = app
|
self.app = app
|
||||||
|
|
||||||
self._search_folders = set()
|
self._search_folders = set()
|
||||||
@@ -79,22 +79,24 @@ class Senpy(object):
|
|||||||
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(
|
||||||
message=("No plugins found."
|
status=404,
|
||||||
" Please install one.").format(algo))
|
message=("No plugins found."
|
||||||
|
" 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(
|
||||||
message=("The algorithm '{}'"
|
status=400,
|
||||||
" is not activated yet").format(algo))
|
message=("The algorithm '{}'"
|
||||||
|
" is not activated yet").format(algo))
|
||||||
plug = self.plugins[algo]
|
plug = self.plugins[algo]
|
||||||
nif_params = parse_params(params, spec=NIF_PARAMS)
|
nif_params = parse_params(params, spec=NIF_PARAMS)
|
||||||
extra_params = plug.get('extra_params', {})
|
extra_params = plug.get('extra_params', {})
|
||||||
@@ -119,9 +121,8 @@ class Senpy(object):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def parameters(self, algo):
|
def parameters(self, algo):
|
||||||
return getattr(self.plugins.get(algo) or self.default_plugin,
|
return getattr(
|
||||||
"extra_params",
|
self.plugins.get(algo) or self.default_plugin, "extra_params", {})
|
||||||
{})
|
|
||||||
|
|
||||||
def activate_all(self, sync=False):
|
def activate_all(self, sync=False):
|
||||||
ps = []
|
ps = []
|
||||||
@@ -145,47 +146,53 @@ 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("Error deactivating plugin {}: {}".format(
|
||||||
ex))
|
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):
|
def reload_plugin(self, name):
|
||||||
logger.debug("Reloading {}".format(name))
|
logger.debug("Reloading {}".format(name))
|
||||||
@@ -198,43 +205,71 @@ class Senpy(object):
|
|||||||
logger.error('Error reloading {}: {}'.format(name, ex))
|
logger.error('Error reloading {}: {}'.format(name, ex))
|
||||||
self.plugins[name] = plugin
|
self.plugins[name] = plugin
|
||||||
|
|
||||||
@staticmethod
|
@classmethod
|
||||||
def _load_plugin(root, filename):
|
def validate_info(cls, info):
|
||||||
logger.debug("Loading plugin: {}".format(filename))
|
return all(x in info for x in ('name', 'module', 'version'))
|
||||||
fpath = os.path.join(root, filename)
|
|
||||||
with open(fpath, 'r') as f:
|
def install_deps(self):
|
||||||
info = json.load(f)
|
for i in self.plugins.values():
|
||||||
logger.debug("Info: {}".format(info))
|
self._install_deps(i._info)
|
||||||
sys.path.append(root)
|
|
||||||
|
@classmethod
|
||||||
|
def _install_deps(cls, info=None):
|
||||||
|
requirements = info.get('requirements', [])
|
||||||
|
if requirements:
|
||||||
|
pip_args = []
|
||||||
|
pip_args.append('install')
|
||||||
|
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:
|
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
|
||||||
repo_path = root
|
module._repo = Repo(repo_path)
|
||||||
module._repo = Repo(repo_path)
|
except InvalidGitRepositoryError:
|
||||||
except InvalidGitRepositoryError:
|
logger.debug("The plugin {} is not in a Git repository".format(
|
||||||
module._repo = None
|
module))
|
||||||
|
module._repo = None
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
logger.error("Exception importing {}: {}".format(filename, ex))
|
logger.error("Exception importing {}: {}".format(module, ex))
|
||||||
logger.error("Trace: {}".format(traceback.format_exc()))
|
logger.error("Trace: {}".format(traceback.format_exc()))
|
||||||
return None, None
|
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:
|
||||||
@@ -262,8 +297,7 @@ 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("matching {} with {}: {}".format(plug.name, kwargs,
|
||||||
kwargs,
|
|
||||||
res))
|
res))
|
||||||
return res
|
return res
|
||||||
|
|
||||||
@@ -274,5 +308,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)
|
||||||
|
}
|
||||||
|
211
senpy/models.py
211
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,40 @@ 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
|
||||||
|
|
||||||
|
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,15 +67,14 @@ 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=True, headers=None, **kwargs):
|
||||||
in_headers=False,
|
|
||||||
headers=None,
|
|
||||||
**kwargs):
|
|
||||||
"""
|
"""
|
||||||
Return the values and error to be used in flask.
|
Return the values and error to be used in flask.
|
||||||
So far, it returns a fixed context. We should store/generate different
|
So far, it returns a fixed context. We should store/generate different
|
||||||
@@ -86,33 +91,34 @@ class SenpyMixin(object):
|
|||||||
'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"' % url)
|
||||||
})
|
})
|
||||||
return FlaskResponse(json.dumps(js, indent=2, sort_keys=True),
|
return FlaskResponse(
|
||||||
status=getattr(self, "status", 200),
|
json.dumps(
|
||||||
headers=headers,
|
js, indent=2, sort_keys=True),
|
||||||
mimetype="application/json")
|
status=getattr(self, "status", 200),
|
||||||
|
headers=headers,
|
||||||
|
mimetype="application/json")
|
||||||
|
|
||||||
def serializable(self):
|
def serializable(self):
|
||||||
def ser_or_down(item):
|
def ser_or_down(item):
|
||||||
if hasattr(item, 'serializable'):
|
if hasattr(item, 'serializable'):
|
||||||
return item.serializable()
|
return item.serializable()
|
||||||
elif isinstance(item, dict):
|
elif isinstance(item, dict):
|
||||||
temp = dict()
|
temp = dict()
|
||||||
for kp in item:
|
for kp in item:
|
||||||
vp = item[kp]
|
vp = item[kp]
|
||||||
temp[kp] = ser_or_down(vp)
|
temp[kp] = ser_or_down(vp)
|
||||||
return temp
|
return temp
|
||||||
elif isinstance(item, list):
|
elif isinstance(item, list):
|
||||||
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, with_context=True, context_uri=None):
|
def jsonld(self, with_context=True, context_uri=None):
|
||||||
ser = self.serializable()
|
ser = self.serializable()
|
||||||
|
|
||||||
if with_context:
|
if with_context:
|
||||||
context = []
|
context = []
|
||||||
if context_uri:
|
if context_uri:
|
||||||
context = context_uri
|
context = context_uri
|
||||||
@@ -120,9 +126,9 @@ class SenpyMixin(object):
|
|||||||
context = self.context.copy()
|
context = self.context.copy()
|
||||||
if hasattr(self, 'prefix'):
|
if hasattr(self, 'prefix'):
|
||||||
# This sets @base for the document, which will be used in
|
# This sets @base for the document, which will be used in
|
||||||
# all relative URIs will. For example, if a uri is "Example" and
|
# all relative URIs. For example, if a uri is "Example" and
|
||||||
# prefix =s "http://example.com", the absolute URI after expanding
|
# prefix =s "http://example.com", the absolute URI after
|
||||||
# with JSON-LD will be "http://example.com/Example"
|
# expanding with JSON-LD will be "http://example.com/Example"
|
||||||
|
|
||||||
prefix_context = {"@base": self.prefix}
|
prefix_context = {"@base": self.prefix}
|
||||||
if isinstance(context, list):
|
if isinstance(context, list):
|
||||||
@@ -132,10 +138,8 @@ class SenpyMixin(object):
|
|||||||
ser["@context"] = context
|
ser["@context"] = context
|
||||||
return ser
|
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 +149,41 @@ 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:
|
||||||
|
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]
|
||||||
|
|
||||||
reqs = self.schema.get('required', [])
|
|
||||||
for i in reqs:
|
|
||||||
if i not in temp:
|
|
||||||
prop = self.schema['properties'][i]
|
|
||||||
if 'default' in prop:
|
|
||||||
temp[i] = copy.deepcopy(prop['default'])
|
|
||||||
if 'context' in temp:
|
if 'context' in temp:
|
||||||
context = temp['context']
|
context = temp['context']
|
||||||
del temp['context']
|
del temp['context']
|
||||||
self.__dict__['context'] = Context.load(context)
|
self.__dict__['context'] = Context.load(context)
|
||||||
super(SenpyModel, self).__init__(temp)
|
try:
|
||||||
|
temp['@type'] = getattr(self, '@type')
|
||||||
|
except AttributeError:
|
||||||
|
logger.warn('Creating an instance of an unknown model')
|
||||||
|
super(BaseModel, self).__init__(temp)
|
||||||
|
|
||||||
def _get_key(self, key):
|
def _get_key(self, key):
|
||||||
key = key.replace("__", ":", 1)
|
key = key.replace("__", ":", 1)
|
||||||
@@ -181,7 +192,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 +207,87 @@ 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] != "_"}
|
||||||
d["@id"] = d.pop('id')
|
if 'id' in d:
|
||||||
|
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_schema(name, schema_file=None, base_classes=None):
|
||||||
schema = read_schema('suggestion.json')
|
base_classes = base_classes or []
|
||||||
|
base_classes.append(BaseModel)
|
||||||
|
schema_file = schema_file or '{}.json'.format(name)
|
||||||
|
class_name = '{}{}'.format(i[0].upper(), i[1:])
|
||||||
|
newclass = type(class_name, tuple(base_classes), {})
|
||||||
|
setattr(newclass, '@type', name)
|
||||||
|
setattr(newclass, 'schema', read_schema(schema_file))
|
||||||
|
register(newclass, name)
|
||||||
|
return newclass
|
||||||
|
|
||||||
class PluginModel(SenpyModel):
|
|
||||||
schema = read_schema('plugin.json')
|
|
||||||
|
|
||||||
class Plugins(SenpyModel):
|
def _add_from_schema(*args, **kwargs):
|
||||||
schema = read_schema('plugins.json')
|
generatedClass = from_schema(*args, **kwargs)
|
||||||
|
globals()[generatedClass.__name__] = generatedClass
|
||||||
|
del generatedClass
|
||||||
|
|
||||||
class Error(SenpyMixin, BaseException ):
|
|
||||||
|
|
||||||
def __init__(self, message, status=500, params=None, errors=None, *args, **kwargs):
|
for i in ['response',
|
||||||
|
'results',
|
||||||
|
'entry',
|
||||||
|
'sentiment',
|
||||||
|
'analysis',
|
||||||
|
'emotionSet',
|
||||||
|
'emotion',
|
||||||
|
'emotionModel',
|
||||||
|
'suggestion',
|
||||||
|
'plugin',
|
||||||
|
'emotionPlugin',
|
||||||
|
'sentimentPlugin',
|
||||||
|
'plugins']:
|
||||||
|
_add_from_schema(i)
|
||||||
|
|
||||||
|
_ErrorModel = from_schema('error')
|
||||||
|
|
||||||
|
|
||||||
|
class Error(SenpyMixin, BaseException):
|
||||||
|
def __init__(self,
|
||||||
|
message,
|
||||||
|
*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 __getattr__(self, key):
|
||||||
return self.__dict__
|
if key != '_error' and hasattr(self._error, key):
|
||||||
|
return getattr(self._error, key)
|
||||||
|
raise AttributeError(key)
|
||||||
|
|
||||||
def __str__(self):
|
def __setattr__(self, key, value):
|
||||||
return str(self.jsonld())
|
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,16 +5,17 @@ import inspect
|
|||||||
import os.path
|
import os.path
|
||||||
import pickle
|
import pickle
|
||||||
import logging
|
import logging
|
||||||
from .models import Response, PluginModel, Error
|
import tempfile
|
||||||
|
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):
|
||||||
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)
|
super(SenpyPlugin, self).__init__(info)
|
||||||
self.id = '{}_{}'.format(self.name, self.version)
|
self.id = '{}_{}'.format(self.name, self.version)
|
||||||
@@ -38,8 +39,8 @@ class SenpyPlugin(PluginModel):
|
|||||||
''' Destructor, to make sure all the resources are freed '''
|
''' Destructor, to make sure all the resources are freed '''
|
||||||
self.deactivate()
|
self.deactivate()
|
||||||
|
|
||||||
class SentimentPlugin(SenpyPlugin):
|
|
||||||
|
|
||||||
|
class SentimentPlugin(SenpyPlugin, models.SentimentPlugin):
|
||||||
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))
|
||||||
@@ -47,17 +48,14 @@ class SentimentPlugin(SenpyPlugin):
|
|||||||
self["@type"] = "marl:SentimentAnalysis"
|
self["@type"] = "marl:SentimentAnalysis"
|
||||||
|
|
||||||
|
|
||||||
class EmotionPlugin(SenpyPlugin):
|
class EmotionPlugin(SentimentPlugin, models.EmotionPlugin):
|
||||||
|
|
||||||
def __init__(self, info, *args, **kwargs):
|
def __init__(self, info, *args, **kwargs):
|
||||||
resp = super(EmotionPlugin, self).__init__(info, *args, **kwargs)
|
|
||||||
self.minEmotionValue = float(info.get("minEmotionValue", 0))
|
self.minEmotionValue = float(info.get("minEmotionValue", 0))
|
||||||
self.maxEmotionValue = float(info.get("maxEmotionValue", 0))
|
self.maxEmotionValue = float(info.get("maxEmotionValue", 0))
|
||||||
self["@type"] = "onyx:EmotionAnalysis"
|
self["@type"] = "onyx:EmotionAnalysis"
|
||||||
|
|
||||||
|
|
||||||
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 +71,18 @@ 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 not hasattr(self, '_shelf_file') or not self._shelf_file:
|
||||||
if hasattr(self, '_info') and 'shelf_file' in self._info:
|
if hasattr(self, '_info') and 'shelf_file' in self._info:
|
||||||
self.__dict__['_shelf_file'] = self._info['shelf_file']
|
self.__dict__['_shelf_file'] = self._info['shelf_file']
|
||||||
else:
|
else:
|
||||||
self._shelf_file = os.path.join(self.get_folder(), self.name + '.p')
|
self._shelf_file = os.path.join(tempfile.gettempdir(),
|
||||||
|
self.name + '.p')
|
||||||
return self._shelf_file
|
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'])
|
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
import json
|
|
||||||
import random
|
import random
|
||||||
|
|
||||||
from senpy.plugins import SentimentPlugin
|
from senpy.plugins import SentimentPlugin
|
||||||
@@ -16,26 +15,15 @@ class Sentiment140Plugin(SentimentPlugin):
|
|||||||
polarity = "marl:Positive"
|
polarity = "marl:Positive"
|
||||||
elif polarity_value < 0:
|
elif polarity_value < 0:
|
||||||
polarity = "marl:Negative"
|
polarity = "marl:Negative"
|
||||||
entry = Entry({"id":":Entry0",
|
entry = Entry({"id": ":Entry0", "nif:isString": params["input"]})
|
||||||
"nif:isString": params["input"]})
|
sentiment = Sentiment({
|
||||||
sentiment = Sentiment({"id": ":Sentiment0",
|
"id": ":Sentiment0",
|
||||||
"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 = []
|
||||||
entry.sentiments.append(sentiment)
|
entry.sentiments.append(sentiment)
|
||||||
entry.language = lang
|
entry.language = lang
|
||||||
response.entries.append(entry)
|
response.entries.append(entry)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@@ -9,16 +9,17 @@ class Sentiment140Plugin(SentimentPlugin):
|
|||||||
def analyse(self, **params):
|
def analyse(self, **params):
|
||||||
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({
|
||||||
"data": [{"text": params["input"]}]
|
"language": lang,
|
||||||
}
|
"data": [{
|
||||||
)
|
"text": params["input"]
|
||||||
)
|
}]
|
||||||
|
}))
|
||||||
|
|
||||||
p = params.get("prefix", None)
|
p = params.get("prefix", None)
|
||||||
response = Results(prefix=p)
|
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"
|
||||||
neutral_value = self.maxPolarityValue / 2.0
|
neutral_value = self.maxPolarityValue / 2.0
|
||||||
if polarity_value > neutral_value:
|
if polarity_value > neutral_value:
|
||||||
@@ -26,12 +27,12 @@ class Sentiment140Plugin(SentimentPlugin):
|
|||||||
elif polarity_value < neutral_value:
|
elif polarity_value < neutral_value:
|
||||||
polarity = "marl:Negative"
|
polarity = "marl:Negative"
|
||||||
|
|
||||||
entry = Entry(id="Entry0",
|
entry = Entry(id="Entry0", nif__isString=params["input"])
|
||||||
nif__isString=params["input"])
|
sentiment = Sentiment(
|
||||||
sentiment = Sentiment(id="Sentiment0",
|
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 = []
|
||||||
entry.sentiments.append(sentiment)
|
entry.sentiments.append(sentiment)
|
||||||
|
7
senpy/schemas/\
Normal file
7
senpy/schemas/\
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"description": "Senpy analysis",
|
||||||
|
"allOf": [{
|
||||||
|
"$ref": "atom.json"
|
||||||
|
}]
|
||||||
|
}
|
@@ -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"]
|
||||||
|
}
|
@@ -16,16 +16,20 @@
|
|||||||
"@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": "prov:wasGeneratedBy"
|
||||||
|
@@ -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"
|
||||||
}
|
}
|
||||||
|
18
senpy/schemas/emotionAnalysis.json
Normal file
18
senpy/schemas/emotionAnalysis.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"description": "Senpy Emotion analysis",
|
||||||
|
"type": "object",
|
||||||
|
"allOf": [
|
||||||
|
{"$ref": "analysis.json"},
|
||||||
|
{"properties":
|
||||||
|
{
|
||||||
|
"onyx:hasEmotionModel": {
|
||||||
|
"anyOf": [
|
||||||
|
{"type": "string"},
|
||||||
|
{"$ref": "emotionModel.json"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["onyx:hasEmotionModel"]
|
||||||
|
}]
|
||||||
|
}
|
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,34 @@
|
|||||||
{
|
{
|
||||||
"$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" }
|
||||||
|
},
|
||||||
|
"emotions": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {"$ref": "emotionSet.json" }
|
||||||
|
},
|
||||||
|
"entities": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {"$ref": "entity.json" }
|
||||||
|
},
|
||||||
|
"topics": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {"$ref": "topic.json" }
|
||||||
|
},
|
||||||
|
"suggestions": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {"$ref": "suggestion.json" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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": "list",
|
||||||
|
"items": {"type": "object"}
|
||||||
|
},
|
||||||
|
"code": {
|
||||||
|
"type": "int"
|
||||||
|
},
|
||||||
|
"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,4 @@
|
|||||||
{
|
{
|
||||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
"$ref": "definitions.json#/Suggestion"
|
"type": "object"
|
||||||
}
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
@@ -60,13 +60,30 @@ body {
|
|||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
|
|
||||||
}
|
}
|
||||||
#input_request {
|
#jsonraw {
|
||||||
display:block;
|
overflow: auto;
|
||||||
width:150px;
|
padding: 20px;
|
||||||
word-wrap:break-word;
|
background: lightgray;
|
||||||
|
-moz-border-radius: 20px;
|
||||||
|
-webkit-border-radius: 20px;
|
||||||
|
-khtml-border-radius: 20px;
|
||||||
|
border-radius: 20px;
|
||||||
}
|
}
|
||||||
textarea
|
#input_request {
|
||||||
{
|
margin-top: 5px;
|
||||||
|
display:block;
|
||||||
|
word-wrap:break-word;
|
||||||
|
white-space:pre;
|
||||||
|
overflow: auto;
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
#input_request a{
|
||||||
|
width:100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
textarea{
|
||||||
border:1px solid #999999;
|
border:1px solid #999999;
|
||||||
width:100%;
|
width:100%;
|
||||||
margin:5px 0;
|
margin:5px 0;
|
||||||
|
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,6 +29,7 @@ $(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"]){
|
if (plugins[r]["name"]){
|
||||||
@@ -62,12 +63,17 @@ $(document).ready(function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var pluginList = document.createElement('li');
|
||||||
|
pluginList.innerHTML = "<a href=https://github.com/gsi-upm/senpy-plugins-community>" + plugins[r]["name"] + "</a>" + ": " + plugins[r]["description"]
|
||||||
|
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();
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -102,8 +108,19 @@ 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 container = document.getElementById('results');
|
||||||
document.getElementById("input_request").innerHTML = "<label>"+url+"</label>"
|
var options = {
|
||||||
|
mode: 'view'
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
container.removeChild(container.firstChild);
|
||||||
|
}
|
||||||
|
catch(err) {
|
||||||
|
}
|
||||||
|
var editor = new JSONEditor(container, options, response);
|
||||||
|
document.getElementById("jsonraw").innerHTML = replaceURLWithHTMLLinks(JSON.stringify(response, undefined, 2))
|
||||||
|
document.getElementById("input_request").innerHTML = "<a href='"+url+"'>"+url+"</a>"
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -10,12 +10,15 @@
|
|||||||
<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">
|
||||||
@@ -45,8 +48,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="col-lg-6 ">
|
|
||||||
<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-sign-in"></i> Follow us on <a href="http://www.github.com/gsi-upm/senpy">GitHub</a></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -54,6 +55,15 @@
|
|||||||
<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>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-lg-6 ">
|
||||||
|
<div class="well">
|
||||||
|
<h2>Available Plugins</h2>
|
||||||
|
<div>
|
||||||
|
<span><ul id=availablePlugins></ul></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -74,10 +84,25 @@ I cannot believe it!</textarea></div>
|
|||||||
<a id="preview" class="btn btn-lg btn-primary" href="#" onclick="load_JSON()">Analyse!</a>
|
<a id="preview" class="btn btn-lg btn-primary" href="#" onclick="load_JSON()">Analyse!</a>
|
||||||
<!--<button id="visualise" name="type" type="button">Visualise!</button>-->
|
<!--<button id="visualise" name="type" type="button">Visualise!</button>-->
|
||||||
</form>
|
</form>
|
||||||
<div id="content">
|
|
||||||
<span id="input_request"></span>
|
<span id="input_request"></span>
|
||||||
<pre id="results"></pre>
|
<ul class="nav nav-tabs" role="tablist">
|
||||||
</div>
|
<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">
|
||||||
|
|
||||||
|
<div class="tab-pane active" id="viewer">
|
||||||
|
<div id="content">
|
||||||
|
<pre id="results"></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-pane" id="raw">
|
||||||
|
<div id="content">
|
||||||
|
<pre id="jsonraw"></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
4
senpy/version.py
Normal file
4
senpy/version.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
with open(os.path.join(os.path.dirname(__file__), 'VERSION')) as f:
|
||||||
|
__version__ = f.read().strip()
|
@@ -2,3 +2,8 @@
|
|||||||
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
|
||||||
|
29
setup.py
29
setup.py
@@ -4,8 +4,10 @@ from pip.req import parse_requirements
|
|||||||
# parse_requirements() returns generator of pip.req.InstallRequirement objects
|
# parse_requirements() returns generator of pip.req.InstallRequirement objects
|
||||||
|
|
||||||
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")
|
||||||
@@ -15,30 +17,27 @@ except AttributeError:
|
|||||||
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]
|
||||||
|
|
||||||
exec(open('senpy/__init__.py').read())
|
from senpy import __version__
|
||||||
|
|
||||||
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,
|
||||||
tests_require=test_reqs,
|
tests_require=test_reqs,
|
||||||
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'
|
})
|
||||||
]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
@@ -3,6 +3,5 @@ from senpy.models import Results
|
|||||||
|
|
||||||
|
|
||||||
class DummyPlugin(SentimentPlugin):
|
class DummyPlugin(SentimentPlugin):
|
||||||
|
|
||||||
def analyse(self, *args, **kwargs):
|
def analyse(self, *args, **kwargs):
|
||||||
return Results()
|
return Results()
|
||||||
|
@@ -4,7 +4,6 @@ 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)
|
||||||
|
|
||||||
|
@@ -1,9 +1,10 @@
|
|||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
|
import json
|
||||||
|
|
||||||
from senpy.extensions import Senpy
|
from senpy.extensions import Senpy
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
from flask.ext.testing import TestCase
|
from unittest import TestCase
|
||||||
from gevent import sleep
|
from gevent import sleep
|
||||||
from itertools import product
|
from itertools import product
|
||||||
|
|
||||||
@@ -12,30 +13,37 @@ 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 json.loads(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
|
|
||||||
|
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, 404)
|
||||||
logging.debug(resp.json)
|
js = parse_resp(resp)
|
||||||
assert resp.json["status"] == 404
|
logging.debug(js)
|
||||||
|
assert js["status"] == 404
|
||||||
atleast = {
|
atleast = {
|
||||||
"status": 404,
|
"status": 404,
|
||||||
"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 +51,93 @@ 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_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"] == "Dummy_0.1"
|
||||||
|
|
||||||
def test_activate(self):
|
def test_activate(self):
|
||||||
""" Activate and deactivate one plugin """
|
""" Activate and deactivate one plugin """
|
||||||
resp = self.client.get("/api/plugins/Dummy/deactivate")
|
resp = self.client.get("/api/plugins/Dummy/deactivate")
|
||||||
self.assert200(resp)
|
self.assertCode(resp, 200)
|
||||||
sleep(0.5)
|
sleep(0.5)
|
||||||
resp = self.client.get("/api/plugins/Dummy/")
|
resp = self.client.get("/api/plugins/Dummy/")
|
||||||
self.assert200(resp)
|
self.assertCode(resp, 200)
|
||||||
assert "is_activated" in resp.json
|
js = parse_resp(resp)
|
||||||
assert resp.json["is_activated"] == False
|
assert "is_activated" in js
|
||||||
|
assert not js["is_activated"]
|
||||||
resp = self.client.get("/api/plugins/Dummy/activate")
|
resp = self.client.get("/api/plugins/Dummy/activate")
|
||||||
self.assert200(resp)
|
self.assertCode(resp, 200)
|
||||||
sleep(0.5)
|
sleep(0.5)
|
||||||
resp = self.client.get("/api/plugins/Dummy/")
|
resp = self.client.get("/api/plugins/Dummy/")
|
||||||
self.assert200(resp)
|
self.assertCode(resp, 200)
|
||||||
assert "is_activated" in resp.json
|
js = parse_resp(resp)
|
||||||
assert resp.json["is_activated"] == True
|
assert "is_activated" in js
|
||||||
|
assert js["is_activated"]
|
||||||
|
|
||||||
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
|
||||||
|
assert js["@id"] == "Dummy_0.1"
|
||||||
resp = self.client.get("/api/plugins/Dummy/deactivate")
|
resp = self.client.get("/api/plugins/Dummy/deactivate")
|
||||||
self.assert200(resp)
|
self.assertCode(resp, 200)
|
||||||
sleep(0.5)
|
sleep(0.5)
|
||||||
resp = self.client.get("/api/plugins/default/")
|
resp = self.client.get("/api/plugins/default/")
|
||||||
self.assert404(resp)
|
self.assertCode(resp, 404)
|
||||||
|
|
||||||
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')
|
||||||
|
42
tests/test_client.py
Normal file
42
tests/test_client.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
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()
|
||||||
|
|
||||||
|
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'})
|
@@ -6,18 +6,16 @@ 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, default_plugins=False)
|
||||||
self.senpy.init_app(self.app)
|
self.senpy.init_app(self.app)
|
||||||
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,6 +32,21 @@ 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, '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) == 2
|
||||||
@@ -75,4 +88,5 @@ class ExtensionsTest(TestCase):
|
|||||||
assert self.senpy.filter_plugins(name="Dummy", is_activated=True)
|
assert self.senpy.filter_plugins(name="Dummy", is_activated=True)
|
||||||
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))
|
||||||
|
@@ -1,20 +1,16 @@
|
|||||||
import os
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import jsonschema
|
import jsonschema
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
from senpy.models import Response, Entry, Results, Sentiment, EmotionSet, Emotion, Error
|
from senpy.models import Entry, Results, Sentiment, EmotionSet, Error
|
||||||
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",
|
prueba = {"id": "test",
|
||||||
"analysis": [],
|
"analysis": [],
|
||||||
"entries": []}
|
"entries": []}
|
||||||
@@ -27,28 +23,32 @@ class ModelsTest(TestCase):
|
|||||||
j = r.jsonld(with_context=True)
|
j = r.jsonld(with_context=True)
|
||||||
print("As JSON:")
|
print("As JSON:")
|
||||||
pprint(j)
|
pprint(j)
|
||||||
assert("@context" in j)
|
assert ("@context" in j)
|
||||||
assert("marl" in j["@context"])
|
assert ("marl" in j["@context"])
|
||||||
assert("entries" in j["@context"])
|
assert ("entries" in j["@context"])
|
||||||
assert(j["@id"] == "test")
|
assert (j["@id"] == "test")
|
||||||
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)
|
||||||
assert("entries" in j6)
|
assert ("entries" in j6)
|
||||||
assert("analysis" in j6)
|
assert ("analysis" in j6)
|
||||||
resp = r6.flask()
|
resp = r6.flask()
|
||||||
received = json.loads(resp.data.decode())
|
received = json.loads(resp.data.decode())
|
||||||
logging.debug("Response: %s", j6)
|
logging.debug("Response: %s", j6)
|
||||||
assert(received["entries"])
|
assert (received["entries"])
|
||||||
assert(received["entries"][0]["nif:isString"] == "Just testing")
|
assert (received["entries"][0]["nif:isString"] == "Just testing")
|
||||||
assert(received["entries"][0]["nif:isString"] != "Not testing")
|
assert (received["entries"][0]["nif:isString"] != "Not testing")
|
||||||
|
|
||||||
def test_id(self):
|
def test_id(self):
|
||||||
''' Adding the id after creation should overwrite the automatic ID
|
''' Adding the id after creation should overwrite the automatic ID
|
||||||
@@ -61,7 +61,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 +102,16 @@ class ModelsTest(TestCase):
|
|||||||
logging.debug(c)
|
logging.debug(c)
|
||||||
p.validate()
|
p.validate()
|
||||||
|
|
||||||
|
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_frame_response(self):
|
def test_frame_response(self):
|
||||||
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={
|
||||||
'version': 'test',
|
'name': 'shelve',
|
||||||
'shelf_file': self.shelf_file})
|
'version': 'test',
|
||||||
|
'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,37 @@ 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()
|
||||||
|
|
||||||
|
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={
|
||||||
'version': 'test',
|
'name': 'shelve',
|
||||||
'shelf_file': self.shelf_file})
|
'version': 'test',
|
||||||
|
'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={
|
||||||
'version': 'test',
|
'name': 'shelve',
|
||||||
'shelf_file': self.shelf_file})
|
'version': 'test',
|
||||||
b.test(key='a', value='fromA')
|
'shelf_file': self.shelf_file
|
||||||
|
})
|
||||||
|
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'
|
||||||
|
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()
|
Reference in New Issue
Block a user