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