Skip to content

Commit b68daa5

Browse files
authored
Merge pull request #6 from brionmario/develop
basic functionality with logging and config support
2 parents 17a283b + 0edcfde commit b68daa5

21 files changed

+34468
-6
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Library testing scripts and assets
2+
cssi/testing
3+
14
# Byte-compiled / optimized / DLL files
25
__pycache__/
36
*.py[cod]

MANIFEST.in

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
1-
include LICENSE.txt
1+
include LICENSE.txt
2+
include cssi/meta/*
3+
include cssi/data/*
4+
include cssi/data/**/*
5+
include cssi/data/**/**/*

cssi/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import logging
2+
from logging import NullHandler
3+
4+
# Add NullHandler to avoid errors if the host application
5+
# doesn't have logging configured.
6+
default_logger = logging.getLogger("cssi.core")
7+
default_logger.addHandler(NullHandler())
8+
9+
# Set the default level to WARN
10+
if default_logger.level == logging.NOTSET:
11+
default_logger.setLevel(logging.WARN)

cssi/config.py

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
4+
# (c) Copyright 2019 Brion Mario.
5+
# (c) This file is part of the CSSI Core library and is made available under MIT license.
6+
# (c) For more information, see https://github.com/brionmario/cssi-core/blob/master/LICENSE.txt
7+
# (c) Please forward any queries to the given email address. email: [email protected]
8+
9+
"""The config module for the CSSI library
10+
11+
Authors:
12+
Brion Mario
13+
14+
"""
15+
16+
import configparser
17+
18+
from cssi.exceptions import CSSIException
19+
20+
DEFAULT_INTERNAL_CONFIG = "default.config.cssi"
21+
22+
23+
class CSSIConfig(object):
24+
"""CSSI library configuration."""
25+
26+
def __init__(self):
27+
# init [latency] section options
28+
self.latency_weight = 0.0
29+
self.latency_boundary = 0.0
30+
31+
# init [sentiment] section options
32+
self.sentiment_weight = 0.0
33+
34+
# init [questionnaire] section options
35+
self.questionnaire_weight = 0.0
36+
37+
# init [plugins] list
38+
self.plugins = []
39+
40+
# init plugin options
41+
self.plugin_options = {}
42+
43+
def read_from_file(self, filename):
44+
"""Read configuration from a file.
45+
46+
A filename can be passed in to load the configurations.
47+
48+
"""
49+
50+
parser = CustomCSSIConfigParser()
51+
try:
52+
parser.read(filename)
53+
except configparser.Error as error:
54+
raise CSSIException("Couldn't read supplied configuration file {0}: {1}".format(filename, error))
55+
56+
status = False
57+
try:
58+
for option_spec in self.CONFIG_FILE_OPTIONS:
59+
was_set = self._set_config_attribute_from_option(parser, *option_spec)
60+
if was_set:
61+
status = True
62+
except ValueError as error:
63+
raise CSSIException("Couldn't read supplied configuration file {0}: {1}".format(filename, error))
64+
65+
# cssi plugin options
66+
for plugin in self.plugins:
67+
if parser.has_section(plugin):
68+
self.plugin_options[plugin] = parser.get_section(plugin)
69+
status = True
70+
71+
return status
72+
73+
CONFIG_FILE_OPTIONS = [
74+
# Arguments for _set_config_attribute_from_option function
75+
76+
# [run]
77+
('plugins', 'run:plugins', 'list'),
78+
79+
# [latency]
80+
('latency_weight', 'latency:latency_weight', 'float'),
81+
('latency_boundary', 'latency:latency_boundary', 'float'),
82+
83+
# [sentiment]
84+
('sentiment', 'sentiment:sentiment_weight', 'float'),
85+
86+
# [questionnaire]
87+
('questionnaire', 'questionnaire:questionnaire_weight', 'float'),
88+
]
89+
90+
def _set_config_attribute_from_option(self, parser, attr, where, type_=''):
91+
"""Sets an attribute on self if it exists in the ConfigParser."""
92+
section, option = where.split(":")
93+
if parser.has_option(section, option):
94+
method = getattr(parser, 'get' + type_)
95+
setattr(self, attr, method(section, option))
96+
return True
97+
return False
98+
99+
def get_plugin_options(self, plugin):
100+
"""Returns a list of options for the plugins named `plugin`."""
101+
return self.plugin_options.get(plugin, {})
102+
103+
def set_option(self, option_name, value):
104+
"""Sets an option in the configuration."""
105+
106+
# Check all the default options.
107+
for option_spec in self.CONFIG_FILE_OPTIONS:
108+
attr, where = option_spec[:2]
109+
if where == option_name:
110+
setattr(self, attr, value)
111+
return
112+
113+
# Checks if it is a plugin option.
114+
plugin_name, _, key = option_name.partition(":")
115+
if key and plugin_name in self.plugins:
116+
self.plugin_options.setdefault(plugin_name, {})[key] = value
117+
return
118+
119+
raise CSSIException("The option was not found {0}".format(option_name))
120+
121+
def get_option(self, option_name):
122+
"""Get an option from the configuration."""
123+
124+
# Check all the default options.
125+
for option_spec in self.CONFIG_FILE_OPTIONS:
126+
attr, where = option_spec[:2]
127+
if where == option_name:
128+
return getattr(self, attr)
129+
130+
# Checks if it is a plugin option.
131+
plugin_name, _, key = option_name.partition(":")
132+
if key and plugin_name in self.plugins:
133+
return self.plugin_options.get(plugin_name, {}).get(key)
134+
135+
raise CSSIException("The option was not found {0}".format(option_name))
136+
137+
138+
class CustomCSSIConfigParser(configparser.RawConfigParser):
139+
"""Custom parser for CSSI configs
140+
141+
This class extends the functionality of the `RawConfigParser` from `configparser`
142+
module. And it is useful for parsing lists and custom configuration options.
143+
144+
"""
145+
146+
def __init__(self):
147+
configparser.RawConfigParser.__init__(self)
148+
149+
def read(self, filenames, encoding=None):
150+
"""Read a file name as UTF-8 configuration data."""
151+
return configparser.RawConfigParser.read(self, filenames=filenames, encoding=encoding)
152+
153+
def has_option(self, section, option):
154+
"""Checks if the config has a section"""
155+
has = configparser.RawConfigParser.has_option(self, section, option)
156+
if has:
157+
return has
158+
return False
159+
160+
def has_section(self, section):
161+
"""Checks if the config has a section"""
162+
has = configparser.RawConfigParser.has_section(self, section)
163+
if has:
164+
return section
165+
return False
166+
167+
def options(self, section):
168+
"""Checks if the config has options in a given section"""
169+
if configparser.RawConfigParser.has_section(self, section):
170+
return configparser.RawConfigParser.options(self, section)
171+
raise configparser.NoSectionError
172+
173+
def get_section(self, section):
174+
"""Returns the contents of a section, as a dictionary.
175+
176+
Important for providing plugin options.
177+
"""
178+
sections = {}
179+
for option in self.options(section):
180+
sections[option] = self.get(section, option)
181+
return sections
182+
183+
def get(self, section, option, *args, **kwargs):
184+
"""Get an option value for a given section."""
185+
val = configparser.RawConfigParser.get(self, section, option, *args, **kwargs)
186+
return val
187+
188+
def getlist(self, section, option):
189+
"""Read a list of strings."""
190+
_list = self.get(section, option)
191+
values = []
192+
for line in _list.split('\n'):
193+
for value in line.split(','):
194+
value = value.strip()
195+
if value:
196+
values.append(value)
197+
return values
198+
199+
200+
def read_cssi_config(filename):
201+
"""Read the CSSI configuration file.
202+
203+
Returns:
204+
config: CSSIConfig object
205+
206+
"""
207+
# load the defaults
208+
config = CSSIConfig()
209+
210+
# read from the file
211+
is_read = config.read_from_file(filename=filename)
212+
213+
# TODO: Log these messages
214+
if not is_read:
215+
config.read_from_file(filename="default.config.cssi")
216+
print("Configurations couldn't be loaded from file: {0}. Rolling back to internal defaults.".format(filename))
217+
else:
218+
print("Configuration was successfully read from file: {0}".format(filename))
219+
220+
return config

cssi/contributors.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from abc import ABC, abstractmethod
2+
3+
4+
class CSSIContributor(ABC):
5+
"""An abstract class for all the CSSI contributors
6+
7+
All the contributors of CSSI score generation should extend this
8+
class and must implement the `generate_score` function.
9+
10+
"""
11+
12+
def __init__(self, config, debug=False):
13+
self.config = config
14+
self.debug = debug
15+
16+
@abstractmethod
17+
def generate_score(self, *args):
18+
""""""
19+
pass

cssi/core.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
4+
# (c) Copyright 2019 Brion Mario.
5+
# (c) This file is part of the CSSI Core library and is made available under MIT license.
6+
# (c) For more information, see https://github.com/brionmario/cssi-core/blob/master/LICENSE.txt
7+
# (c) Please forward any queries to the given email address. email: [email protected]
8+
9+
"""The main access point for the CSSI library
10+
11+
Authors:
12+
Brion Mario
13+
14+
"""
15+
import os
16+
import logging
17+
# `Keras` was hanging when trying to use the model.predict
18+
# and swapping the backend from `Tensorflow` to `Theano`
19+
# fixed the issue.
20+
os.environ['KERAS_BACKEND'] = 'theano'
21+
22+
from cssi.config import read_cssi_config
23+
from cssi.latency import Latency
24+
from cssi.sentiment import Sentiment
25+
26+
logger = logging.getLogger(__name__)
27+
28+
29+
class CSSI(object):
30+
31+
def __init__(self, shape_predictor, debug=False, config_file=None):
32+
if config_file is None:
33+
self.config_file = "config.cssi"
34+
self.config_file = config_file
35+
self.debug = debug
36+
self.config = read_cssi_config(filename=self.config_file)
37+
self.latency = Latency(config=self.config, debug=self.debug, shape_predictor=shape_predictor)
38+
self.sentiment = Sentiment(config=self.config, debug=self.debug)
39+
logger.debug("Initialized CSSI library")
40+
41+
42+

0 commit comments

Comments
 (0)