commit
f6f51d35a5
@ -0,0 +1,9 @@ |
|||||||
|
_work_/ |
||||||
|
|
||||||
|
.venv* |
||||||
|
*.pyc |
||||||
|
.pytest_cache/ |
||||||
|
*.egg-info/ |
||||||
|
|
||||||
|
*.swp |
||||||
|
*.swo |
@ -0,0 +1,549 @@ |
|||||||
|
[MASTER] |
||||||
|
|
||||||
|
# A comma-separated list of package or module names from where C extensions may |
||||||
|
# be loaded. Extensions are loading into the active Python interpreter and may |
||||||
|
# run arbitrary code |
||||||
|
extension-pkg-whitelist=PyQt5 |
||||||
|
|
||||||
|
# Add files or directories to the blacklist. They should be base names, not |
||||||
|
# paths. |
||||||
|
ignore=CVS |
||||||
|
|
||||||
|
# Add files or directories matching the regex patterns to the blacklist. The |
||||||
|
# regex matches against base names, not paths. |
||||||
|
ignore-patterns= |
||||||
|
|
||||||
|
# Python code to execute, usually for sys.path manipulation such as |
||||||
|
# pygtk.require(). |
||||||
|
#init-hook= |
||||||
|
|
||||||
|
# Use multiple processes to speed up Pylint. |
||||||
|
jobs=1 |
||||||
|
|
||||||
|
# List of plugins (as comma separated values of python modules names) to load, |
||||||
|
# usually to register additional checkers. |
||||||
|
load-plugins= |
||||||
|
|
||||||
|
# Pickle collected data for later comparisons. |
||||||
|
persistent=yes |
||||||
|
|
||||||
|
# Specify a configuration file. |
||||||
|
#rcfile= |
||||||
|
|
||||||
|
# When enabled, pylint would attempt to guess common misconfiguration and emit |
||||||
|
# user-friendly hints instead of false-positive error messages |
||||||
|
suggestion-mode=yes |
||||||
|
|
||||||
|
# Allow loading of arbitrary C extensions. Extensions are imported into the |
||||||
|
# active Python interpreter and may run arbitrary code. |
||||||
|
unsafe-load-any-extension=no |
||||||
|
|
||||||
|
|
||||||
|
[MESSAGES CONTROL] |
||||||
|
|
||||||
|
# Only show warnings with the listed confidence levels. Leave empty to show |
||||||
|
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED |
||||||
|
confidence= |
||||||
|
|
||||||
|
# Disable the message, report, category or checker with the given id(s). You |
||||||
|
# can either give multiple identifiers separated by comma (,) or put this |
||||||
|
# option multiple times (only on the command line, not in the configuration |
||||||
|
# file where it should appear only once).You can also use "--disable=all" to |
||||||
|
# disable everything first and then reenable specific checks. For example, if |
||||||
|
# you want to run only the similarities checker, you can use "--disable=all |
||||||
|
# --enable=similarities". If you want to run only the classes checker, but have |
||||||
|
# no Warning level messages displayed, use"--disable=all --enable=classes |
||||||
|
# --disable=W" |
||||||
|
disable=print-statement, |
||||||
|
parameter-unpacking, |
||||||
|
unpacking-in-except, |
||||||
|
old-raise-syntax, |
||||||
|
backtick, |
||||||
|
long-suffix, |
||||||
|
old-ne-operator, |
||||||
|
old-octal-literal, |
||||||
|
import-star-module-level, |
||||||
|
non-ascii-bytes-literal, |
||||||
|
invalid-unicode-literal, |
||||||
|
raw-checker-failed, |
||||||
|
bad-inline-option, |
||||||
|
locally-disabled, |
||||||
|
locally-enabled, |
||||||
|
file-ignored, |
||||||
|
suppressed-message, |
||||||
|
useless-suppression, |
||||||
|
deprecated-pragma, |
||||||
|
apply-builtin, |
||||||
|
basestring-builtin, |
||||||
|
buffer-builtin, |
||||||
|
cmp-builtin, |
||||||
|
coerce-builtin, |
||||||
|
execfile-builtin, |
||||||
|
file-builtin, |
||||||
|
long-builtin, |
||||||
|
raw_input-builtin, |
||||||
|
reduce-builtin, |
||||||
|
standarderror-builtin, |
||||||
|
unicode-builtin, |
||||||
|
xrange-builtin, |
||||||
|
coerce-method, |
||||||
|
delslice-method, |
||||||
|
getslice-method, |
||||||
|
setslice-method, |
||||||
|
no-absolute-import, |
||||||
|
old-division, |
||||||
|
dict-iter-method, |
||||||
|
dict-view-method, |
||||||
|
next-method-called, |
||||||
|
metaclass-assignment, |
||||||
|
indexing-exception, |
||||||
|
raising-string, |
||||||
|
reload-builtin, |
||||||
|
oct-method, |
||||||
|
hex-method, |
||||||
|
nonzero-method, |
||||||
|
cmp-method, |
||||||
|
input-builtin, |
||||||
|
round-builtin, |
||||||
|
intern-builtin, |
||||||
|
unichr-builtin, |
||||||
|
map-builtin-not-iterating, |
||||||
|
zip-builtin-not-iterating, |
||||||
|
range-builtin-not-iterating, |
||||||
|
filter-builtin-not-iterating, |
||||||
|
using-cmp-argument, |
||||||
|
eq-without-hash, |
||||||
|
div-method, |
||||||
|
idiv-method, |
||||||
|
rdiv-method, |
||||||
|
exception-message-attribute, |
||||||
|
invalid-str-codec, |
||||||
|
sys-max-int, |
||||||
|
bad-python3-import, |
||||||
|
deprecated-string-function, |
||||||
|
deprecated-str-translate-call, |
||||||
|
deprecated-itertools-function, |
||||||
|
deprecated-types-field, |
||||||
|
next-method-defined, |
||||||
|
dict-items-not-iterating, |
||||||
|
dict-keys-not-iterating, |
||||||
|
dict-values-not-iterating, |
||||||
|
deprecated-operator-function, |
||||||
|
deprecated-urllib-function, |
||||||
|
xreadlines-attribute, |
||||||
|
deprecated-sys-function, |
||||||
|
exception-escape, |
||||||
|
comprehension-escape, |
||||||
|
bad-whitespace, |
||||||
|
invalid-name, |
||||||
|
wrong-import-position |
||||||
|
|
||||||
|
# Enable the message, report, category or checker with the given id(s). You can |
||||||
|
# either give multiple identifier separated by comma (,) or put this option |
||||||
|
# multiple time (only on the command line, not in the configuration file where |
||||||
|
# it should appear only once). See also the "--disable" option for examples. |
||||||
|
enable=c-extension-no-member |
||||||
|
|
||||||
|
|
||||||
|
[REPORTS] |
||||||
|
|
||||||
|
# Python expression which should return a note less than 10 (10 is the highest |
||||||
|
# note). You have access to the variables errors warning, statement which |
||||||
|
# respectively contain the number of errors / warnings messages and the total |
||||||
|
# number of statements analyzed. This is used by the global evaluation report |
||||||
|
# (RP0004). |
||||||
|
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) |
||||||
|
|
||||||
|
# Template used to display messages. This is a python new-style format string |
||||||
|
# used to format the message information. See doc for all details |
||||||
|
#msg-template= |
||||||
|
|
||||||
|
# Set the output format. Available formats are text, parseable, colorized, json |
||||||
|
# and msvs (visual studio).You can also give a reporter class, eg |
||||||
|
# mypackage.mymodule.MyReporterClass. |
||||||
|
output-format=colorized |
||||||
|
|
||||||
|
# Tells whether to display a full report or only the messages |
||||||
|
reports=no |
||||||
|
|
||||||
|
# Activate the evaluation score. |
||||||
|
score=yes |
||||||
|
|
||||||
|
|
||||||
|
[REFACTORING] |
||||||
|
|
||||||
|
# Maximum number of nested blocks for function / method body |
||||||
|
max-nested-blocks=5 |
||||||
|
|
||||||
|
# Complete name of functions that never returns. When checking for |
||||||
|
# inconsistent-return-statements if a never returning function is called then |
||||||
|
# it will be considered as an explicit return statement and no message will be |
||||||
|
# printed. |
||||||
|
never-returning-functions=optparse.Values,sys.exit |
||||||
|
|
||||||
|
|
||||||
|
[VARIABLES] |
||||||
|
|
||||||
|
# List of additional names supposed to be defined in builtins. Remember that |
||||||
|
# you should avoid to define new builtins when possible. |
||||||
|
additional-builtins= |
||||||
|
|
||||||
|
# Tells whether unused global variables should be treated as a violation. |
||||||
|
allow-global-unused-variables=yes |
||||||
|
|
||||||
|
# List of strings which can identify a callback function by name. A callback |
||||||
|
# name must start or end with one of those strings. |
||||||
|
callbacks=cb_, |
||||||
|
_cb |
||||||
|
|
||||||
|
# A regular expression matching the name of dummy variables (i.e. expectedly |
||||||
|
# not used). |
||||||
|
dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ |
||||||
|
|
||||||
|
# Argument names that match this expression will be ignored. Default to name |
||||||
|
# with leading underscore |
||||||
|
ignored-argument-names=_.*|^ignored_|^unused_ |
||||||
|
|
||||||
|
# Tells whether we should check for unused import in __init__ files. |
||||||
|
init-import=no |
||||||
|
|
||||||
|
# List of qualified module names which can have objects that can redefine |
||||||
|
# builtins. |
||||||
|
redefining-builtins-modules=six.moves,past.builtins,future.builtins,io,builtins |
||||||
|
|
||||||
|
|
||||||
|
[TYPECHECK] |
||||||
|
|
||||||
|
# List of decorators that produce context managers, such as |
||||||
|
# contextlib.contextmanager. Add to this list to register other decorators that |
||||||
|
# produce valid context managers. |
||||||
|
contextmanager-decorators=contextlib.contextmanager |
||||||
|
|
||||||
|
# List of members which are set dynamically and missed by pylint inference |
||||||
|
# system, and so shouldn't trigger E1101 when accessed. Python regular |
||||||
|
# expressions are accepted. |
||||||
|
generated-members= |
||||||
|
|
||||||
|
# Tells whether missing members accessed in mixin class should be ignored. A |
||||||
|
# mixin class is detected if its name ends with "mixin" (case insensitive). |
||||||
|
ignore-mixin-members=yes |
||||||
|
|
||||||
|
# This flag controls whether pylint should warn about no-member and similar |
||||||
|
# checks whenever an opaque object is returned when inferring. The inference |
||||||
|
# can return multiple potential results while evaluating a Python object, but |
||||||
|
# some branches might not be evaluated, which results in partial inference. In |
||||||
|
# that case, it might be useful to still emit no-member and other checks for |
||||||
|
# the rest of the inferred objects. |
||||||
|
ignore-on-opaque-inference=yes |
||||||
|
|
||||||
|
# List of class names for which member attributes should not be checked (useful |
||||||
|
# for classes with dynamically set attributes). This supports the use of |
||||||
|
# qualified names. |
||||||
|
ignored-classes=optparse.Values,thread._local,_thread._local |
||||||
|
|
||||||
|
# List of module names for which member attributes should not be checked |
||||||
|
# (useful for modules/projects where namespaces are manipulated during runtime |
||||||
|
# and thus existing member attributes cannot be deduced by static analysis. It |
||||||
|
# supports qualified module names, as well as Unix pattern matching. |
||||||
|
ignored-modules= |
||||||
|
|
||||||
|
# Show a hint with possible names when a member name was not found. The aspect |
||||||
|
# of finding the hint is based on edit distance. |
||||||
|
missing-member-hint=yes |
||||||
|
|
||||||
|
# The minimum edit distance a name should have in order to be considered a |
||||||
|
# similar match for a missing member name. |
||||||
|
missing-member-hint-distance=1 |
||||||
|
|
||||||
|
# The total number of similar names that should be taken in consideration when |
||||||
|
# showing a hint for a missing member. |
||||||
|
missing-member-max-choices=1 |
||||||
|
|
||||||
|
|
||||||
|
[FORMAT] |
||||||
|
|
||||||
|
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. |
||||||
|
expected-line-ending-format= |
||||||
|
|
||||||
|
# Regexp for a line that is allowed to be longer than the limit. |
||||||
|
ignore-long-lines=^\s*(# )?<?https?://\S+>?$ |
||||||
|
|
||||||
|
# Number of spaces of indent required inside a hanging or continued line. |
||||||
|
indent-after-paren=4 |
||||||
|
|
||||||
|
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 |
||||||
|
# tab). |
||||||
|
indent-string=' ' |
||||||
|
|
||||||
|
# Maximum number of characters on a single line. |
||||||
|
max-line-length=120 |
||||||
|
|
||||||
|
# Maximum number of lines in a module |
||||||
|
max-module-lines=1000 |
||||||
|
|
||||||
|
# List of optional constructs for which whitespace checking is disabled. `dict- |
||||||
|
# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. |
||||||
|
# `trailing-comma` allows a space between comma and closing bracket: (a, ). |
||||||
|
# `empty-line` allows space-only lines. |
||||||
|
no-space-check=trailing-comma, |
||||||
|
dict-separator |
||||||
|
|
||||||
|
# Allow the body of a class to be on the same line as the declaration if body |
||||||
|
# contains single statement. |
||||||
|
single-line-class-stmt=no |
||||||
|
|
||||||
|
# Allow the body of an if to be on the same line as the test if there is no |
||||||
|
# else. |
||||||
|
single-line-if-stmt=no |
||||||
|
|
||||||
|
|
||||||
|
[LOGGING] |
||||||
|
|
||||||
|
# Logging modules to check that the string format arguments are in logging |
||||||
|
# function parameter format |
||||||
|
logging-modules=logging |
||||||
|
|
||||||
|
|
||||||
|
[BASIC] |
||||||
|
|
||||||
|
# Naming style matching correct argument names |
||||||
|
argument-naming-style=snake_case |
||||||
|
|
||||||
|
# Regular expression matching correct argument names. Overrides argument- |
||||||
|
# naming-style |
||||||
|
#argument-rgx= |
||||||
|
|
||||||
|
# Naming style matching correct attribute names |
||||||
|
attr-naming-style=snake_case |
||||||
|
|
||||||
|
# Regular expression matching correct attribute names. Overrides attr-naming- |
||||||
|
# style |
||||||
|
#attr-rgx= |
||||||
|
|
||||||
|
# Bad variable names which should always be refused, separated by a comma |
||||||
|
bad-names=foo, |
||||||
|
bar, |
||||||
|
baz, |
||||||
|
toto, |
||||||
|
tutu, |
||||||
|
tata |
||||||
|
|
||||||
|
# Naming style matching correct class attribute names |
||||||
|
class-attribute-naming-style=any |
||||||
|
|
||||||
|
# Regular expression matching correct class attribute names. Overrides class- |
||||||
|
# attribute-naming-style |
||||||
|
#class-attribute-rgx= |
||||||
|
|
||||||
|
# Naming style matching correct class names |
||||||
|
class-naming-style=PascalCase |
||||||
|
|
||||||
|
# Regular expression matching correct class names. Overrides class-naming-style |
||||||
|
#class-rgx= |
||||||
|
|
||||||
|
# Naming style matching correct constant names |
||||||
|
const-naming-style=UPPER_CASE |
||||||
|
|
||||||
|
# Regular expression matching correct constant names. Overrides const-naming- |
||||||
|
# style |
||||||
|
#const-rgx= |
||||||
|
|
||||||
|
# Minimum line length for functions/classes that require docstrings, shorter |
||||||
|
# ones are exempt. |
||||||
|
docstring-min-length=-1 |
||||||
|
|
||||||
|
# Naming style matching correct function names |
||||||
|
function-naming-style=snake_case |
||||||
|
|
||||||
|
# Regular expression matching correct function names. Overrides function- |
||||||
|
# naming-style |
||||||
|
#function-rgx= |
||||||
|
|
||||||
|
# Good variable names which should always be accepted, separated by a comma |
||||||
|
good-names=i, |
||||||
|
j, |
||||||
|
k, |
||||||
|
ex, |
||||||
|
Run, |
||||||
|
_ |
||||||
|
|
||||||
|
# Include a hint for the correct naming format with invalid-name |
||||||
|
include-naming-hint=no |
||||||
|
|
||||||
|
# Naming style matching correct inline iteration names |
||||||
|
inlinevar-naming-style=any |
||||||
|
|
||||||
|
# Regular expression matching correct inline iteration names. Overrides |
||||||
|
# inlinevar-naming-style |
||||||
|
#inlinevar-rgx= |
||||||
|
|
||||||
|
# Naming style matching correct method names |
||||||
|
method-naming-style=snake_case |
||||||
|
|
||||||
|
# Regular expression matching correct method names. Overrides method-naming- |
||||||
|
# style |
||||||
|
#method-rgx= |
||||||
|
|
||||||
|
# Naming style matching correct module names |
||||||
|
module-naming-style=snake_case |
||||||
|
|
||||||
|
# Regular expression matching correct module names. Overrides module-naming- |
||||||
|
# style |
||||||
|
#module-rgx= |
||||||
|
|
||||||
|
# Colon-delimited sets of names that determine each other's naming style when |
||||||
|
# the name regexes allow several styles. |
||||||
|
name-group= |
||||||
|
|
||||||
|
# Regular expression which should only match function or class names that do |
||||||
|
# not require a docstring. |
||||||
|
no-docstring-rgx=^_ |
||||||
|
|
||||||
|
# List of decorators that produce properties, such as abc.abstractproperty. Add |
||||||
|
# to this list to register other decorators that produce valid properties. |
||||||
|
property-classes=abc.abstractproperty |
||||||
|
|
||||||
|
# Naming style matching correct variable names |
||||||
|
variable-naming-style=snake_case |
||||||
|
|
||||||
|
# Regular expression matching correct variable names. Overrides variable- |
||||||
|
# naming-style |
||||||
|
#variable-rgx= |
||||||
|
|
||||||
|
|
||||||
|
[SIMILARITIES] |
||||||
|
|
||||||
|
# Ignore comments when computing similarities. |
||||||
|
ignore-comments=yes |
||||||
|
|
||||||
|
# Ignore docstrings when computing similarities. |
||||||
|
ignore-docstrings=yes |
||||||
|
|
||||||
|
# Ignore imports when computing similarities. |
||||||
|
ignore-imports=no |
||||||
|
|
||||||
|
# Minimum lines number of a similarity. |
||||||
|
min-similarity-lines=4 |
||||||
|
|
||||||
|
|
||||||
|
[SPELLING] |
||||||
|
|
||||||
|
# Limits count of emitted suggestions for spelling mistakes |
||||||
|
max-spelling-suggestions=4 |
||||||
|
|
||||||
|
# Spelling dictionary name. Available dictionaries: none. To make it working |
||||||
|
# install python-enchant package. |
||||||
|
spelling-dict= |
||||||
|
|
||||||
|
# List of comma separated words that should not be checked. |
||||||
|
spelling-ignore-words= |
||||||
|
|
||||||
|
# A path to a file that contains private dictionary; one word per line. |
||||||
|
spelling-private-dict-file= |
||||||
|
|
||||||
|
# Tells whether to store unknown words to indicated private dictionary in |
||||||
|
# --spelling-private-dict-file option instead of raising a message. |
||||||
|
spelling-store-unknown-words=no |
||||||
|
|
||||||
|
|
||||||
|
[MISCELLANEOUS] |
||||||
|
|
||||||
|
# List of note tags to take in consideration, separated by a comma. |
||||||
|
notes=FIXME, |
||||||
|
XXX, |
||||||
|
TODO |
||||||
|
|
||||||
|
|
||||||
|
[CLASSES] |
||||||
|
|
||||||
|
# List of method names used to declare (i.e. assign) instance attributes. |
||||||
|
defining-attr-methods=__init__, |
||||||
|
__new__, |
||||||
|
setUp |
||||||
|
|
||||||
|
# List of member names, which should be excluded from the protected access |
||||||
|
# warning. |
||||||
|
exclude-protected=_asdict, |
||||||
|
_fields, |
||||||
|
_replace, |
||||||
|
_source, |
||||||
|
_make |
||||||
|
|
||||||
|
# List of valid names for the first argument in a class method. |
||||||
|
valid-classmethod-first-arg=cls |
||||||
|
|
||||||
|
# List of valid names for the first argument in a metaclass class method. |
||||||
|
valid-metaclass-classmethod-first-arg=mcs |
||||||
|
|
||||||
|
|
||||||
|
[DESIGN] |
||||||
|
|
||||||
|
# Maximum number of arguments for function / method |
||||||
|
max-args=8 |
||||||
|
|
||||||
|
# Maximum number of attributes for a class (see R0902). |
||||||
|
max-attributes=7 |
||||||
|
|
||||||
|
# Maximum number of boolean expressions in a if statement |
||||||
|
max-bool-expr=5 |
||||||
|
|
||||||
|
# Maximum number of branch for function / method body |
||||||
|
max-branches=12 |
||||||
|
|
||||||
|
# Maximum number of locals for function / method body |
||||||
|
max-locals=15 |
||||||
|
|
||||||
|
# Maximum number of parents for a class (see R0901). |
||||||
|
max-parents=7 |
||||||
|
|
||||||
|
# Maximum number of public methods for a class (see R0904). |
||||||
|
max-public-methods=20 |
||||||
|
|
||||||
|
# Maximum number of return / yield for function / method body |
||||||
|
max-returns=6 |
||||||
|
|
||||||
|
# Maximum number of statements in function / method body |
||||||
|
max-statements=50 |
||||||
|
|
||||||
|
# Minimum number of public methods for a class (see R0903). |
||||||
|
min-public-methods=2 |
||||||
|
|
||||||
|
|
||||||
|
[IMPORTS] |
||||||
|
|
||||||
|
# Allow wildcard imports from modules that define __all__. |
||||||
|
allow-wildcard-with-all=no |
||||||
|
|
||||||
|
# Analyse import fallback blocks. This can be used to support both Python 2 and |
||||||
|
# 3 compatible code, which means that the block might have code that exists |
||||||
|
# only in one or another interpreter, leading to false positives when analysed. |
||||||
|
analyse-fallback-blocks=no |
||||||
|
|
||||||
|
# Deprecated modules which should not be used, separated by a comma |
||||||
|
deprecated-modules=optparse,tkinter.tix |
||||||
|
|
||||||
|
# Create a graph of external dependencies in the given file (report RP0402 must |
||||||
|
# not be disabled) |
||||||
|
ext-import-graph= |
||||||
|
|
||||||
|
# Create a graph of every (i.e. internal and external) dependencies in the |
||||||
|
# given file (report RP0402 must not be disabled) |
||||||
|
import-graph= |
||||||
|
|
||||||
|
# Create a graph of internal dependencies in the given file (report RP0402 must |
||||||
|
# not be disabled) |
||||||
|
int-import-graph= |
||||||
|
|
||||||
|
# Force import order to recognize a module as part of the standard |
||||||
|
# compatibility libraries. |
||||||
|
known-standard-library= |
||||||
|
|
||||||
|
# Force import order to recognize a module as part of a third party library. |
||||||
|
known-third-party=enchant |
||||||
|
|
||||||
|
|
||||||
|
[EXCEPTIONS] |
||||||
|
|
||||||
|
# Exceptions that will emit a warning when being caught. Defaults to |
||||||
|
# "Exception" |
||||||
|
overgeneral-exceptions=Exception |
@ -0,0 +1,3 @@ |
|||||||
|
recursive-include vasl_templates/webapp/config *.* |
||||||
|
recursive-include vasl_templates/webapp/static *.* |
||||||
|
recursive-include vasl_templates/webapp/templates *.* |
@ -0,0 +1,2 @@ |
|||||||
|
[pytest] |
||||||
|
addopts = --pylint |
@ -0,0 +1,33 @@ |
|||||||
|
""" Setup the package. |
||||||
|
|
||||||
|
Install this module in development mode to get the tests to work: |
||||||
|
pip install --editable .[dev] |
||||||
|
""" |
||||||
|
|
||||||
|
from setuptools import setup, find_packages |
||||||
|
|
||||||
|
setup( |
||||||
|
name = "vasl_templates", |
||||||
|
version = "0.1", |
||||||
|
packages = find_packages(), |
||||||
|
install_requires = [ |
||||||
|
# Python 3.6.5 |
||||||
|
"flask==1.0.2", |
||||||
|
# NOTE: PyQt5 requirements: https://doc.qt.io/qt-5/linux.html |
||||||
|
# Linux: mesa-libGL-devel ; @"C Development Tools and Libraries" |
||||||
|
# nb: WebEngine seems to be broken in 5.10.1 :-/ |
||||||
|
"PyQT5==5.10.0", |
||||||
|
], |
||||||
|
extras_require = { |
||||||
|
"dev": [ |
||||||
|
"pytest==3.6.0", |
||||||
|
"selenium==3.12.0", |
||||||
|
"pylint==1.9.2", |
||||||
|
"pytest-pylint==0.9.0", |
||||||
|
], |
||||||
|
}, |
||||||
|
include_package_data = True, |
||||||
|
entry_points = { |
||||||
|
"console_scripts": "vasl-templates = vasl_templates.main:main", |
||||||
|
} |
||||||
|
) |
@ -0,0 +1,87 @@ |
|||||||
|
#!/usr/bin/env python3 |
||||||
|
""" Main entry point for the application. """ |
||||||
|
|
||||||
|
import sys |
||||||
|
import threading |
||||||
|
import traceback |
||||||
|
import logging |
||||||
|
import urllib.request |
||||||
|
|
||||||
|
from PyQt5.QtWidgets import QApplication |
||||||
|
from PyQt5.QtCore import Qt |
||||||
|
|
||||||
|
from vasl_templates.main_window import MainWindow |
||||||
|
from vasl_templates.webapp import app as webapp |
||||||
|
|
||||||
|
# --------------------------------------------------------------------- |
||||||
|
|
||||||
|
class LoggerProxy: |
||||||
|
"""Redirect messages to Python logging.""" |
||||||
|
def __init__( self, logger, level ): |
||||||
|
self.logger = logger |
||||||
|
self.level = level |
||||||
|
self.closed = False |
||||||
|
def write( self, msg ): |
||||||
|
"""Output a message.""" |
||||||
|
if isinstance(msg, bytes): |
||||||
|
msg = msg.decode( "utf-8" ) |
||||||
|
msg = msg.rstrip() |
||||||
|
if msg: |
||||||
|
self.logger.log( self.level, msg ) |
||||||
|
def flush( self ): |
||||||
|
"""Flush the output stream.""" |
||||||
|
pass |
||||||
|
|
||||||
|
# --------------------------------------------------------------------- |
||||||
|
|
||||||
|
def main(): |
||||||
|
"""Main entry point for the application.""" |
||||||
|
|
||||||
|
# connect stdout/stderr to Python logging |
||||||
|
sys.stdout = LoggerProxy( logging, logging.INFO ) |
||||||
|
sys.stderr = LoggerProxy( logging, logging.WARNING ) |
||||||
|
|
||||||
|
# start the webapp server |
||||||
|
port = webapp.config["FLASK_PORT_NO"] |
||||||
|
def webapp_thread(): |
||||||
|
"""Run the webapp server.""" |
||||||
|
try: |
||||||
|
webapp.run( host="localhost", port=port, use_reloader=False ) |
||||||
|
except Exception as ex: |
||||||
|
logging.critical( "WEBAPP SERVER EXCEPTION: %s", ex ) |
||||||
|
logging.critical( traceback.format_exc() ) |
||||||
|
raise |
||||||
|
thread = threading.Thread( target=webapp_thread ) |
||||||
|
thread.start() |
||||||
|
|
||||||
|
# check if we should disable OpenGL |
||||||
|
# Using the QWebEngineView crashes on Windows 7 in a VM. It uses OpenGL, which is |
||||||
|
# apparently not well supported on Windows, and is dependent on the graphics card driver: |
||||||
|
# https://stackoverflow.com/a/50393872 |
||||||
|
# https://stackoverflow.com/questions/33090346/is-there-any-way-to-use-qtwebengine-without-opengl |
||||||
|
# Switching to software rendering (AA_UseSoftwareOpenGL) got things going :shrug: |
||||||
|
# Also see: https://doc.qt.io/qt-5/windows-requirements.html |
||||||
|
opengl_type = webapp.config.get( "OPENGL_TYPE" ) |
||||||
|
if opengl_type: |
||||||
|
logging.info( "Setting OpenGL: %s", opengl_type ) |
||||||
|
opengl_type = getattr( Qt, opengl_type ) |
||||||
|
QApplication.setAttribute( opengl_type ) |
||||||
|
|
||||||
|
# run the application |
||||||
|
app = QApplication( sys.argv ) |
||||||
|
url = "http://localhost:{}/main".format( port ) |
||||||
|
main_window = MainWindow( url ) |
||||||
|
main_window.show() |
||||||
|
ret_code = app.exec_() |
||||||
|
|
||||||
|
# shutdown the webapp server |
||||||
|
url = "http://localhost:{}/shutdown".format( port ) |
||||||
|
urllib.request.urlopen( url ).read() |
||||||
|
thread.join() |
||||||
|
|
||||||
|
return ret_code |
||||||
|
|
||||||
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
||||||
|
|
||||||
|
if __name__ == "__main__": |
||||||
|
sys.exit( main() ) |
@ -0,0 +1,38 @@ |
|||||||
|
""" Main application window. """ |
||||||
|
|
||||||
|
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel |
||||||
|
from PyQt5.QtWebEngineWidgets import QWebEngineView |
||||||
|
from PyQt5.QtGui import QDesktopServices |
||||||
|
from PyQt5.QtCore import QUrl |
||||||
|
|
||||||
|
from vasl_templates.webapp.config.constants import APP_NAME |
||||||
|
from vasl_templates.webapp import app as webapp |
||||||
|
|
||||||
|
# --------------------------------------------------------------------- |
||||||
|
|
||||||
|
class MainWindow( QWidget ): |
||||||
|
"""Main application window.""" |
||||||
|
|
||||||
|
def __init__( self, url ): |
||||||
|
|
||||||
|
# initialize |
||||||
|
super().__init__() |
||||||
|
self.setWindowTitle( APP_NAME ) |
||||||
|
|
||||||
|
# initialize the layout |
||||||
|
# FUDGE! We offer the option to disable the QWebEngineView since getting it to run |
||||||
|
# under Windows (especially older versions) is unreliable (since it uses OpenGL). |
||||||
|
# By disabling it, the program will at least start (in particular, the webapp server), |
||||||
|
# and non-technical users can then open an external browser and connect to the webapp |
||||||
|
# that way. Sigh... |
||||||
|
layout = QVBoxLayout( self ) |
||||||
|
if not webapp.config.get( "DISABLE_WEBENGINEVIEW" ): |
||||||
|
# load the webapp |
||||||
|
browser = QWebEngineView() |
||||||
|
layout.addWidget( browser ) |
||||||
|
browser.setUrl( QUrl(url) ) |
||||||
|
else: |
||||||
|
label = QLabel() |
||||||
|
label.setText( "Running the {} application.\n\nClose this window when you're done.".format( APP_NAME ) ) |
||||||
|
layout.addWidget( label ) |
||||||
|
QDesktopServices.openUrl( QUrl(url) ) |
@ -0,0 +1,46 @@ |
|||||||
|
""" Initialize the package. """ |
||||||
|
|
||||||
|
import os |
||||||
|
import configparser |
||||||
|
import json |
||||||
|
import logging |
||||||
|
|
||||||
|
from flask import Flask |
||||||
|
|
||||||
|
from vasl_templates.webapp.config.constants import APP_NAME, BASE_DIR |
||||||
|
|
||||||
|
# --------------------------------------------------------------------- |
||||||
|
|
||||||
|
# initialize Flask |
||||||
|
app = Flask( __name__ ) |
||||||
|
|
||||||
|
# load the application configuration |
||||||
|
config_dir = os.path.join( BASE_DIR, "config" ) |
||||||
|
config_parser = configparser.ConfigParser() |
||||||
|
config_parser.optionxform = str # preserve case for the keys :-/ |
||||||
|
config_parser.read( os.path.join( config_dir, "app.cfg" ) ) |
||||||
|
app.config.update( dict( config_parser.items( "System" ) ) ) |
||||||
|
fname = os.path.join( config_dir, "debug.cfg" ) |
||||||
|
if os.path.isfile( fname ) : |
||||||
|
config_parser.read( fname ) |
||||||
|
app.config.update( dict( config_parser.items( "Debug" ) ) ) |
||||||
|
|
||||||
|
# initialize logging |
||||||
|
fname = os.path.join( config_dir, "logging.cfg" ) |
||||||
|
if os.path.isfile( fname ): |
||||||
|
import logging.config |
||||||
|
logging.config.dictConfig( json.load( open(fname,"r") ) ) |
||||||
|
|
||||||
|
# load the application |
||||||
|
import vasl_templates.webapp.main #pylint: disable=cyclic-import |
||||||
|
import vasl_templates.webapp.generate #pylint: disable=cyclic-import |
||||||
|
|
||||||
|
# initialize the application |
||||||
|
logger = logging.getLogger( "startup" ) |
||||||
|
|
||||||
|
# --------------------------------------------------------------------- |
||||||
|
|
||||||
|
@app.context_processor |
||||||
|
def inject_template_params(): |
||||||
|
"""Inject template parameters into Jinja2.""" |
||||||
|
return { "APP_NAME": APP_NAME } |
@ -0,0 +1,2 @@ |
|||||||
|
debug.cfg |
||||||
|
logging.cfg |
@ -0,0 +1,3 @@ |
|||||||
|
[System] |
||||||
|
|
||||||
|
FLASK_PORT_NO = 5010 |
@ -0,0 +1,8 @@ |
|||||||
|
""" Application constants. """ |
||||||
|
|
||||||
|
import os |
||||||
|
|
||||||
|
APP_NAME = "VASL Templates" |
||||||
|
APP_VERSION = "v0.1" |
||||||
|
|
||||||
|
BASE_DIR = os.path.abspath( os.path.join( os.path.split(__file__)[0], ".." ) ) |
@ -0,0 +1,15 @@ |
|||||||
|
""" Webapp handlers. """ |
||||||
|
|
||||||
|
from flask import request |
||||||
|
|
||||||
|
from vasl_templates.webapp import app |
||||||
|
|
||||||
|
# --------------------------------------------------------------------- |
||||||
|
|
||||||
|
@app.route( "/generate", methods=["POST"] ) |
||||||
|
def generate_html(): |
||||||
|
"""Generate a response""" |
||||||
|
val = request.form.get( "val" ) |
||||||
|
if val: |
||||||
|
val = val.strip() |
||||||
|
return "You said: {}".format( '"{}"'.format(val) if val else "nothing!" ) |
@ -0,0 +1,20 @@ |
|||||||
|
""" Main webapp handlers. """ |
||||||
|
|
||||||
|
from flask import request, render_template |
||||||
|
|
||||||
|
from vasl_templates.webapp import app |
||||||
|
|
||||||
|
# --------------------------------------------------------------------- |
||||||
|
|
||||||
|
@app.route( "/main" ) |
||||||
|
def main(): |
||||||
|
"""Return the main page.""" |
||||||
|
return render_template( "main.html" ) |
||||||
|
|
||||||
|
# --------------------------------------------------------------------- |
||||||
|
|
||||||
|
@app.route( "/shutdown" ) |
||||||
|
def shutdown(): |
||||||
|
"""Shutdown the webapp (for testing porpoises).""" |
||||||
|
request.environ.get( "werkzeug.server.shutdown" )() |
||||||
|
return "" |
@ -0,0 +1,27 @@ |
|||||||
|
#!/usr/bin/env python3 |
||||||
|
""" Run the webapp server. """ |
||||||
|
|
||||||
|
import os |
||||||
|
import glob |
||||||
|
|
||||||
|
# --------------------------------------------------------------------- |
||||||
|
|
||||||
|
# monitor extra files for changes |
||||||
|
extra_files = [] |
||||||
|
for fspec in ["config","static","templates"] : |
||||||
|
fspec = os.path.abspath( os.path.join( os.path.dirname(__file__), fspec ) ) |
||||||
|
if os.path.isdir( fspec ): |
||||||
|
files = [ os.path.join(fspec,f) for f in os.listdir(fspec) ] |
||||||
|
files = [ f for f in files if os.path.isfile(f) and os.path.splitext(f)[1] not in [".swp"] ] |
||||||
|
else: |
||||||
|
files = glob.glob( fspec ) |
||||||
|
extra_files.extend( files ) |
||||||
|
|
||||||
|
# run the server |
||||||
|
from vasl_templates.webapp import app |
||||||
|
app.run( |
||||||
|
host = "localhost", |
||||||
|
port = app.config["FLASK_PORT_NO"], |
||||||
|
debug = True, |
||||||
|
extra_files = extra_files |
||||||
|
) |
@ -0,0 +1 @@ |
|||||||
|
* { margin: 0 ; padding: 0 } |
File diff suppressed because one or more lines are too long
@ -0,0 +1,19 @@ |
|||||||
|
|
||||||
|
// --------------------------------------------------------------------
|
||||||
|
|
||||||
|
$(document).ready( function () { |
||||||
|
$("form").submit( function( evt ) { |
||||||
|
evt.preventDefault() ; |
||||||
|
var data = { val: $("form input[name='val']").val() } ; |
||||||
|
$.post( { |
||||||
|
url: gGenerateURL, |
||||||
|
data: data, |
||||||
|
success: function( data ) { |
||||||
|
$("#response").text( data ) ; |
||||||
|
}, |
||||||
|
error: function( xhr, status, errorMsg ) { |
||||||
|
$("#response").text( "ERROR: "+errorMsg ) ; |
||||||
|
}, |
||||||
|
} ) ; |
||||||
|
} ) ; |
||||||
|
} ) ; |
@ -0,0 +1,29 @@ |
|||||||
|
<!doctype html> |
||||||
|
<html lang="en"> |
||||||
|
|
||||||
|
<head> |
||||||
|
<meta charset="utf-8"> |
||||||
|
<title> {{APP_NAME}} </title> |
||||||
|
<link rel="shortcut icon" href="{{url_for('static', filename='images/favicon.ico')}}"> |
||||||
|
<link rel="stylesheet" type="text/css" href="{{url_for('static',filename='css/main.css')}}" /> |
||||||
|
</head> |
||||||
|
|
||||||
|
<body> |
||||||
|
|
||||||
|
<form action="/generate"> |
||||||
|
Say something: |
||||||
|
<input type="text" name="val" size="20"> |
||||||
|
<input type="submit" value="GO"> |
||||||
|
</form> |
||||||
|
|
||||||
|
<div id="response"> </div> |
||||||
|
|
||||||
|
</body> |
||||||
|
|
||||||
|
<script src="{{url_for('static',filename='jquery/jquery-3.3.1.min.js')}}"></script> |
||||||
|
<script> |
||||||
|
gGenerateURL = "{{url_for('generate_html')}}" ; |
||||||
|
</script> |
||||||
|
<script src="{{url_for('static',filename='main.js')}}"></script> |
||||||
|
|
||||||
|
</html> |
@ -0,0 +1,74 @@ |
|||||||
|
""" pytest support functions. """ |
||||||
|
|
||||||
|
import os |
||||||
|
import threading |
||||||
|
import logging |
||||||
|
import tempfile |
||||||
|
import urllib.request |
||||||
|
import pytest |
||||||
|
|
||||||
|
from flask import url_for |
||||||
|
|
||||||
|
from vasl_templates.webapp import app |
||||||
|
app.testing = True |
||||||
|
|
||||||
|
FLASK_WEBAPP_PORT = 5001 |
||||||
|
|
||||||
|
# --------------------------------------------------------------------- |
||||||
|
|
||||||
|
@pytest.fixture |
||||||
|
def webapp(): |
||||||
|
"""Launch the webapp.""" |
||||||
|
|
||||||
|
# initialize |
||||||
|
# WTF?! https://github.com/pallets/flask/issues/824 |
||||||
|
def make_webapp_url( endpoint ): |
||||||
|
"""Generate a webapp URL.""" |
||||||
|
with app.test_request_context(): |
||||||
|
url = url_for( endpoint, _external=True ) |
||||||
|
return url.replace( "localhost/", "localhost:{}/".format(FLASK_WEBAPP_PORT) ) |
||||||
|
app.url_for = make_webapp_url |
||||||
|
|
||||||
|
# start the webapp server (in a background thread) |
||||||
|
logging.disable( logging.CRITICAL ) |
||||||
|
thread = threading.Thread( |
||||||
|
target = lambda: app.run( host="0.0.0.0", port=FLASK_WEBAPP_PORT, use_reloader=False ) |
||||||
|
) |
||||||
|
thread.start() |
||||||
|
yield app |
||||||
|
|
||||||
|
# shutdown the webapp server |
||||||
|
urllib.request.urlopen( app.url_for("shutdown") ).read() |
||||||
|
thread.join() |
||||||
|
|
||||||
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
||||||
|
|
||||||
|
@pytest.fixture |
||||||
|
def test_client(): |
||||||
|
"""Return a test client that can be used to connect to the webapp.""" |
||||||
|
logging.disable( logging.CRITICAL ) |
||||||
|
return app.test_client() |
||||||
|
|
||||||
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
||||||
|
|
||||||
|
@pytest.fixture |
||||||
|
def webdriver(): |
||||||
|
"""Return a webdriver that can be used to control a browser. |
||||||
|
|
||||||
|
It would be nice to be able to drive the embedded browser in the wrapper |
||||||
|
Qt app with e.g. this: |
||||||
|
https://github.com/cisco-open-source/qtwebdriver |
||||||
|
but it only works with the old QtWebKit, which was removed in Qt 5.6 :-/ |
||||||
|
""" |
||||||
|
|
||||||
|
# initialize |
||||||
|
from selenium import webdriver as wb |
||||||
|
driver = wb.Firefox( |
||||||
|
log_path = os.path.join( tempfile.gettempdir(), "geckodriver.log" ) |
||||||
|
) |
||||||
|
|
||||||
|
# return the webdriver to the caller |
||||||
|
yield driver |
||||||
|
|
||||||
|
# clean up |
||||||
|
driver.close() |
@ -0,0 +1,37 @@ |
|||||||
|
""" Test response generation. """ |
||||||
|
|
||||||
|
from vasl_templates.webapp.tests.utils import find_child |
||||||
|
|
||||||
|
# --------------------------------------------------------------------- |
||||||
|
|
||||||
|
def test_generate( webapp, webdriver ): |
||||||
|
"""Test response generation.""" |
||||||
|
|
||||||
|
# initialize |
||||||
|
webdriver.get( webapp.url_for( "main" ) ) |
||||||
|
|
||||||
|
# try saying something |
||||||
|
textbox = find_child( webdriver, "input[type='text']" ) |
||||||
|
textbox.clear() |
||||||
|
textbox.send_keys( "Hi, there!" ) |
||||||
|
submit = find_child( webdriver, "input[type='submit']" ) |
||||||
|
submit.click() |
||||||
|
response = find_child( webdriver, "#response" ) |
||||||
|
assert response.text == 'You said: "Hi, there!"' |
||||||
|
|
||||||
|
# try saying something else |
||||||
|
textbox = find_child( webdriver, "input[type='text']" ) |
||||||
|
textbox.clear() |
||||||
|
textbox.send_keys( "Yo mama so big..." ) |
||||||
|
submit = find_child( webdriver, "input[type='submit']" ) |
||||||
|
submit.click() |
||||||
|
response = find_child( webdriver, "#response" ) |
||||||
|
assert response.text == 'You said: "Yo mama so big..."' |
||||||
|
|
||||||
|
# try saying nothing |
||||||
|
textbox = find_child( webdriver, "input[type='text']" ) |
||||||
|
textbox.clear() |
||||||
|
submit = find_child( webdriver, "input[type='submit']" ) |
||||||
|
submit.click() |
||||||
|
response = find_child( webdriver, "#response" ) |
||||||
|
assert response.text == "You said: nothing!" |
@ -0,0 +1,19 @@ |
|||||||
|
""" Helper utilities. """ |
||||||
|
|
||||||
|
from selenium.common.exceptions import NoSuchElementException |
||||||
|
|
||||||
|
# --------------------------------------------------------------------- |
||||||
|
|
||||||
|
def find_child( elem, sel ): |
||||||
|
"""Find a single child element.""" |
||||||
|
try: |
||||||
|
return elem.find_element_by_css_selector( sel ) |
||||||
|
except NoSuchElementException: |
||||||
|
return None |
||||||
|
|
||||||
|
def find_children( elem, sel ): |
||||||
|
"""Find child elements.""" |
||||||
|
try: |
||||||
|
return elem.find_elements_by_css_selector( sel ) |
||||||
|
except NoSuchElementException: |
||||||
|
return None |
Loading…
Reference in new issue