1
0
mirror of https://github.com/gsi-upm/senpy synced 2025-08-24 02:22:20 +00:00

WIP: working on a full refactor for v2.0

This is still not functional, because it involves a LOT of changes to
the basic structure of the project. Some of the main changes can be seen
in the CHANGELOG.md file, if you're interested, but it boils down to
simplifying the logic of plugins (no more activation/deactivation
shenanigans), more robust typing and use of schemas (pydantic) to
avoid inconsistencies and user errors.
This commit is contained in:
J. Fernando Sánchez
2024-12-13 00:01:27 +01:00
parent 9414b0e3e6
commit 54e4dcd5d4
54 changed files with 919 additions and 1487 deletions

View File

@@ -1,53 +0,0 @@
#
# Copyright 2014 Grupo de Sistemas Inteligentes (GSI) DIT, UPM
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from senpy import AnalysisPlugin
import multiprocessing
def _train(process_number):
return process_number
class Async(AnalysisPlugin):
'''An example of an asynchronous module'''
author = '@balkian'
version = '0.2'
sync = False
def _do_async(self, num_processes):
pool = multiprocessing.Pool(processes=num_processes)
values = sorted(pool.map(_train, range(num_processes)))
return values
def activate(self):
self.value = self._do_async(4)
def analyse_entry(self, entry, params):
values = self._do_async(2)
entry.async_values = values
yield entry
test_cases = [
{
'input': 'any',
'expected': {
'async_values': [0, 1]
}
}
]

View File

@@ -24,8 +24,8 @@ import basic
class BasicAnalyseEntry(plugins.SentimentPlugin):
'''Equivalent to Basic, implementing the analyse_entry method'''
author = '@balkian'
version = '0.1'
author: str = '@balkian'
version: str = '0.1'
mappings = {
'pos': 'marl:Positive',
@@ -43,7 +43,7 @@ class BasicAnalyseEntry(plugins.SentimentPlugin):
entry.sentiments.append(s)
yield entry
test_cases = [{
test_cases: list[dict] = [{
'input': 'Hello :)',
'polarity': 'marl:Positive'
}, {

View File

@@ -24,8 +24,8 @@ import basic
class BasicBox(SentimentBox):
''' A modified version of Basic that also does converts annotations manually'''
author = '@balkian'
version = '0.1'
author: str = '@balkian'
version: str = '0.1'
def predict_one(self, features, **kwargs):
output = basic.get_polarity(features[0])
@@ -35,7 +35,7 @@ class BasicBox(SentimentBox):
return [0, 0, 1]
return [0, 1, 0]
test_cases = [{
test_cases: list[dict] = [{
'input': 'Hello :)',
'polarity': 'marl:Positive'
}, {

View File

@@ -25,8 +25,8 @@ import basic
class Basic(SentimentBox):
'''Provides sentiment annotation using a lexicon'''
author = '@balkian'
version = '0.1'
author: str = '@balkian'
version: str = '0.1'
def predict_one(self, features, **kwargs):
output = basic.get_polarity(features[0])
@@ -36,7 +36,7 @@ class Basic(SentimentBox):
return [0, 1, 0]
return [0, 0, 1]
test_cases = [{
test_cases: list[dict] = [{
'input': u'Hello :)',
'polarity': 'marl:Positive'
}, {

View File

@@ -25,8 +25,8 @@ import basic
class Dictionary(plugins.SentimentPlugin):
'''Sentiment annotation using a configurable lexicon'''
author = '@balkian'
version = '0.2'
author: str = '@balkian'
version: str = '0.2'
dictionaries = [basic.emojis, basic.emoticons]
@@ -42,7 +42,7 @@ class Dictionary(plugins.SentimentPlugin):
entry.sentiments.append(s)
yield entry
test_cases = [{
test_cases: list[dict] = [{
'input': 'Hello :)',
'polarity': 'marl:Positive'
}, {
@@ -61,7 +61,7 @@ class EmojiOnly(Dictionary):
'''Sentiment annotation with a basic lexicon of emojis'''
dictionaries = [basic.emojis]
test_cases = [{
test_cases: list[dict] = [{
'input': 'Hello :)',
'polarity': 'marl:Neutral'
}, {
@@ -80,7 +80,7 @@ class EmoticonsOnly(Dictionary):
'''Sentiment annotation with a basic lexicon of emoticons'''
dictionaries = [basic.emoticons]
test_cases = [{
test_cases: list[dict] = [{
'input': 'Hello :)',
'polarity': 'marl:Positive'
}, {
@@ -102,7 +102,7 @@ class Salutes(Dictionary):
'marl:Negative': ['Good bye', ]
}]
test_cases = [{
test_cases: list[dict] = [{
'input': 'Hello :)',
'polarity': 'marl:Positive'
}, {

View File

@@ -19,15 +19,15 @@ from senpy import AnalysisPlugin, easy
class Dummy(AnalysisPlugin):
'''This is a dummy self-contained plugin'''
author = '@balkian'
version = '0.1'
author: str = '@balkian'
version: str = '0.1'
def analyse_entry(self, entry, params):
entry['nif:isString'] = entry['nif:isString'][::-1]
entry.text = entry.text[::-1]
entry.reversed = entry.get('reversed', 0) + 1
yield entry
test_cases = [{
test_cases: list[dict] = [{
'entry': {
'nif:isString': 'Hello',
},

View File

@@ -19,9 +19,9 @@ from senpy import AnalysisPlugin, easy
class DummyRequired(AnalysisPlugin):
'''This is a dummy self-contained plugin'''
author = '@balkian'
version = '0.1'
extra_params = {
author: str = '@balkian'
version: str = '0.1'
extra_params: dict = {
'example': {
'description': 'An example parameter',
'required': True,
@@ -30,11 +30,11 @@ class DummyRequired(AnalysisPlugin):
}
def analyse_entry(self, entry, params):
entry['nif:isString'] = entry['nif:isString'][::-1]
entry.text = entry.text[::-1]
entry.reversed = entry.get('reversed', 0) + 1
yield entry
test_cases = [{
test_cases: list[dict] = [{
'entry': {
'nif:isString': 'Hello',
},

View File

@@ -22,11 +22,11 @@ from senpy.models import EmotionSet, Emotion, Entry
class EmoRand(EmotionPlugin):
'''A sample plugin that returns a random emotion annotation'''
name = 'emotion-random'
author = '@balkian'
version = '0.1'
url = "https://github.com/gsi-upm/senpy-plugins-community"
onyx__usesEmotionModel = "emoml:big6"
name: str = 'emotion-random'
author: str = '@balkian'
version: str = '0.1'
url: str = "https://github.com/gsi-upm/senpy-plugins-community"
usesEmotionModel: str = "emoml:big6"
def analyse_entry(self, entry, activity):
category = "emoml:big6happiness"

View File

@@ -0,0 +1,63 @@
@prefix : <http://www.gsi.upm.es/ontologies/amor/examples#> .
@prefix amor: <http://www.gsi.upm.es/ontologies/amor/ns#> .
@prefix amor-bhv: <http://www.gsi.upm.es/ontologies/amor/models/bhv/ns#> .
@prefix amor-mft: <http://www.gsi.upm.es/ontologies/amor/models/mft/ns#> .
@prefix bhv: <http://www.gsi.upm.es/ontologies/bhv#> .
@prefix mft: <http://www.gsi.upm.es/ontologies/mft/ns#> .
@prefix mls: <http://www.w3.org/ns/mls#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix prov: <http://www.w3.org/ns/prov#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix schema: <http://schema.org/> .
:news1 a owl:NamedIndividual, schema:NewsArticle ;
schema:articleBody "Director Comey says the probe into last year's US election would assess if crimes were committed."^^xsd:string ;
schema:datePublished "2017-03-20T20:30:54+00:00"^^schema:Date ;
schema:headline "Trump Russia claims: FBI's Comey confirms investigation of election 'interference'"^^xsd:string ;
schema:image <http://ichef-1.bbci.co.uk/news/560/media/images/75306000/jpg/_75306515_line976.jpg>,
<http://ichef.bbci.co.uk/news/560/cpsprodpb/8AB9/production/_95231553_comey2.jpg>,
<http://ichef.bbci.co.uk/news/560/cpsprodpb/17519/production/_95231559_committee.jpg>,
<http://ichef.bbci.co.uk/news/560/cpsprodpb/CC81/production/_95235325_f704a6dc-c017-4971-aac3-04c03eb097fb.jpg>,
<http://ichef-1.bbci.co.uk/news/560/cpsprodpb/11AA1/production/_95235327_c0b59f9e-316e-4641-aa7e-3fec6daea62b.jpg>,
<http://ichef.bbci.co.uk/news/560/cpsprodpb/0F99/production/_95239930_trumptweet.png>,
<http://ichef-1.bbci.co.uk/news/560/cpsprodpb/10DFA/production/_95241196_mediaitem95241195.jpg>,
<http://ichef.bbci.co.uk/news/560/cpsprodpb/2CA0/production/_95242411_comey.jpg>,
<http://ichef.bbci.co.uk/news/560/cpsprodpb/11318/production/_95242407_mediaitem95242406.jpg>,
<http://ichef-1.bbci.co.uk/news/560/cpsprodpb/BCED/production/_92856384_line976.jpg>,
<http://ichef-1.bbci.co.uk/news/560/cpsprodpb/12B64/production/_95244667_mediaitem95244666.jpg> ;
schema:mainEntityOfPage <http://www.bbc.com/news/world-us-canada-39324587> ;
schema:publisher :bbc ;
schema:url <http://www.bbc.com/news/world-us-canada-39324587> .
:bbc a schema:Organization ;
schema:logo <http://www.bbc.co.uk/news/special/2015/newsspec_10857/bbc_news_logo.png?cb=1> ;
schema:name "BBC News"^^xsd:string .
:robot1 a prov:SoftwareAgent .
:model1 a mls:Model .
:logisticRegression a mls:Algorithm ;
rdfs:label "Logistic Regression"@en ,
"Regresión Logística"@es .
:run1 a mls:Run ;
mls:executes :wekaLogistic ;
mls:hasInput :credit-a ;
mls:hasOutput :model1 ;
mls:realizes :logisticRegression .
:analysis3 a amor:MoralValueAnalysis ;
prov:wasAssociatedWith :robot1 ;
amor:usedMoralValueModel amor-mft:MoralFoundationsTheory ;
amor:analysed :news1 ;
amor:usedMLModel :model1 ;
prov:generated :annotation3 .
:annotation3 a amor:MoralValueAnnotation ;
amor:hasMoralValueCategory mft:Authority ;
amor:confidence "0.75"^^xsd:float ;
amor-mft:hasPolarityIntensity "0.2"^^xsd:float ;
amor:annotated :news1 .

View File

@@ -0,0 +1,10 @@
from senpy.ns import amor, amor_bhv, amor_mft, prov
from senpy.plugins import MoralityPlugin
class DummyMoralityPlugin(MoralityPlugin):
moralValueModel: str = amor_mft["MoralFoundationTHeory"]
def annotate(self, entry, activity, **kwargs):
yield MoralAnnotation(amor_bhv['Conservation'],
confidence=1.8)

View File

@@ -21,7 +21,7 @@ from senpy.plugins import SentimentPlugin
class NoOp(SentimentPlugin):
'''This plugin does nothing. Literally nothing.'''
version = 0
version: str = 0
def analyse_entry(self, entry, *args, **kwargs):
yield entry

View File

@@ -24,11 +24,10 @@ import basic
class ParameterizedDictionary(plugins.SentimentPlugin):
'''This is a basic self-contained plugin'''
author: str = '@balkian'
version: str = '0.2'
author = '@balkian'
version = '0.2'
extra_params = {
extra_params: dict = {
'positive-words': {
'description': 'Comma-separated list of words that are considered positive',
'aliases': ['positive'],
@@ -56,7 +55,7 @@ class ParameterizedDictionary(plugins.SentimentPlugin):
entry.sentiments.append(s)
yield entry
test_cases = [
test_cases: list[dict] = [
{
'input': 'Hello :)',
'polarity': 'marl:Positive',

View File

@@ -20,12 +20,12 @@ from senpy import SentimentPlugin, Sentiment, Entry
class RandSent(SentimentPlugin):
'''A sample plugin that returns a random sentiment annotation'''
name = 'sentiment-random'
author = "@balkian"
version = '0.1'
url = "https://github.com/gsi-upm/senpy-plugins-community"
marl__maxPolarityValue = '1'
marl__minPolarityValue = "-1"
name: str = 'sentiment-random'
author: str = "@balkian"
version: str = '0.1'
url: str = "https://github.com/gsi-upm/senpy-plugins-community"
maxPolarityValue: float = 1
minPolarityValue: float = -1
def analyse_entry(self, entry, activity):
polarity_value = max(-1, min(1, random.gauss(0.2, 0.2)))

View File

@@ -22,8 +22,8 @@ from mypipeline import pipeline
class PipelineSentiment(SentimentBox):
'''This is a pipeline plugin that wraps a classifier defined in another module
(mypipeline).'''
author = '@balkian'
version = 0.1
author: str = '@balkian'
version: str = 0.1
maxPolarityValue = 1
minPolarityValue = -1
@@ -32,7 +32,7 @@ class PipelineSentiment(SentimentBox):
return [1, 0, 0]
return [0, 0, 1]
test_cases = [
test_cases: list[dict] = [
{
'input': 'The sentiment for senpy should be positive :)',
'polarity': 'marl:Positive'

View File

@@ -20,10 +20,10 @@ from time import sleep
class Sleep(AnalysisPlugin):
'''Dummy plugin to test async'''
author = "@balkian"
version = "0.2"
author: str = "@balkian"
version: str = "0.2"
timeout = 0.05
extra_params = {
extra_params: dict = {
"timeout": {
"@id": "timeout_sleep",
"aliases": ["timeout", "to"],
@@ -32,7 +32,8 @@ class Sleep(AnalysisPlugin):
}
}
def activate(self, *args, **kwargs):
def __init__(self, **kwargs):
super().__init__(**kwargs)
sleep(self.timeout)
def analyse_entry(self, entry, params):