From 8fd8dc549e47a13021f394fa4aea3a728d4e3418 Mon Sep 17 00:00:00 2001 From: Michael Lazar Date: Sat, 5 Dec 2015 01:51:05 -0800 Subject: [PATCH] More pylint fixes, added pylint to the build process. --- .pylintrc | 378 ++++++++++++++++++++++++++++++++++++++++ .travis.yml | 3 +- rtv/content.py | 20 +-- rtv/oauth.py | 2 +- rtv/page.py | 5 +- rtv/submission.py | 18 +- rtv/subreddit.py | 12 +- rtv/subscription.py | 2 +- rtv/terminal.py | 12 +- tests/test_subreddit.py | 6 +- tox.ini | 2 + 11 files changed, 422 insertions(+), 38 deletions(-) create mode 100644 .pylintrc diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..cfa32ee --- /dev/null +++ b/.pylintrc @@ -0,0 +1,378 @@ +[MASTER] + +# Specify a configuration file. +#rcfile= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Pickle collected data for later comparisons. +persistent=yes + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + +# Use multiple processes to speed up Pylint. +jobs=1 + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + +# 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= + +# Allow optimization of some AST trees. This will activate a peephole AST +# optimizer, which will apply various small optimizations. For instance, it can +# be used to obtain the result of joining multiple strings with the addition +# operator. Joining a lot of strings can lead to a maximum recursion error in +# Pylint and this flag can prevent that. It has one side effect, the resulting +# AST will be different than the one from reality. +optimize-ast=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED +confidence= + +# 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. See also the "--disable" option for examples. +#enable= + +# 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=execfile-builtin,zip-builtin-not-iterating,range-builtin-not-iterating,hex-method,old-division,file-builtin,long-builtin,input-builtin,no-absolute-import,invalid-name,delslice-method,suppressed-message,coerce-builtin,buffer-builtin,import-star-module-level,round-builtin,old-ne-operator,apply-builtin,missing-final-newline,basestring-builtin,xrange-builtin,getslice-method,filter-builtin-not-iterating,map-builtin-not-iterating,raw_input-builtin,indexing-exception,dict-iter-method,metaclass-assignment,setslice-method,next-method-called,intern-builtin,using-cmp-argument,missing-docstring,oct-method,backtick,print-statement,reload-builtin,long-suffix,old-raise-syntax,unicode-builtin,nonzero-method,old-octal-literal,cmp-method,useless-suppression,dict-view-method,parameter-unpacking,unpacking-in-except,coerce-method,unichr-builtin,raising-string,cmp-builtin,reduce-builtin,standarderror-builtin + + +[REPORTS] + +# Set the output format. Available formats are text, parseable, colorized, msvs +# (visual studio) and html. You can also give a reporter class, eg +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Put messages in a separate file for each module / package specified on the +# command line instead of printing them on stdout. Reports (if any) will be +# written in a file name "pylint_global.[txt|html]". +files-output=no + +# Tells whether to display a full report or only the messages +reports=yes + +# 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= + + +[SIMILARITIES] + +# Minimum lines number of a similarity. +min-similarity-lines=5 + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,XXX,TODO + + +[VARIABLES] + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# A regular expression matching the name of dummy variables (i.e. expectedly +# not used). +dummy-variables-rgx=_$|dummy + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + +# 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 + + +[LOGGING] + +# Logging modules to check that the string format arguments are in logging +# function parameter format +logging-modules=logging + + +[SPELLING] + +# 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 + + +[TYPECHECK] + +# 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 + +# 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= + +# List of classes names for which member attributes should not be checked +# (useful for classes with attributes dynamically set). This supports can work +# with qualified names. +ignored-classes=SQLObject + +# 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=REQUEST,acl_users,aq_parent + + +[FORMAT] + +# Maximum number of characters on a single line. +max-line-length=100 + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# 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 + +# 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 + +# Maximum number of lines in a module +max-module-lines=1000 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + + +[BASIC] + +# List of builtins function names that should not be used, separated by a comma +bad-functions=map,filter + +# Good variable names which should always be accepted, separated by a comma +good-names=i,j,k,ex,Run,_ + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar,baz,toto,tutu,tata + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Include a hint for the correct naming format with invalid-name +include-naming-hint=no + +# Regular expression matching correct module names +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Naming hint for module names +module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression matching correct variable names +variable-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for variable names +variable-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct class names +class-rgx=[A-Z_][a-zA-Z0-9]+$ + +# Naming hint for class names +class-name-hint=[A-Z_][a-zA-Z0-9]+$ + +# Regular expression matching correct attribute names +attr-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for attribute names +attr-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct constant names +const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Naming hint for constant names +const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Regular expression matching correct class attribute names +class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Naming hint for class attribute names +class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Regular expression matching correct argument names +argument-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for argument names +argument-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct function names +function-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for function names +function-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct method names +method-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for method names +method-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct inline iteration names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Naming hint for inline iteration names +inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=__.*__ + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + + +[ELIF] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + + +[IMPORTS] + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=stringprep,optparse + +# 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 external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled) +int-import-graph= + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp + +# 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 + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict,_fields,_replace,_source,_make + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=5 + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore +ignored-argument-names=_.* + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of branch for function / method body +max-branches=12 + +# Maximum number of statements in function / method body +max-statements=50 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of boolean expressions in a if statement +max-bool-expr=5 + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +overgeneral-exceptions=Exception diff --git a/.travis.yml b/.travis.yml index 97385de..081d022 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,11 +5,12 @@ python: - "3.4" - "3.5" before_install: - - pip install coveralls pytest coverage mock + - pip install coveralls pytest coverage mock pylint - pip install git+https://github.com/kevin1024/vcrpy.git install: - pip install . script: + - pylint --rcfile .pylintrc -E rtv/ - coverage run -m py.test -v after_success: - coveralls diff --git a/rtv/content.py b/rtv/content.py index 9dd8015..0e128d1 100644 --- a/rtv/content.py +++ b/rtv/content.py @@ -76,7 +76,7 @@ class Content(object): if isinstance(comment, praw.objects.MoreComments): data['type'] = 'MoreComments' data['count'] = comment.count - data['body'] = 'More comments'.format(comment.count) + data['body'] = 'More comments' else: author = getattr(comment, 'author', '[deleted]') name = getattr(author, 'name', '[deleted]') @@ -89,7 +89,7 @@ class Content(object): data['type'] = 'Comment' data['body'] = comment.body data['created'] = cls.humanize_timestamp(comment.created_utc) - data['score'] = '{} pts'.format(comment.score) + data['score'] = '{0} pts'.format(comment.score) data['author'] = name data['is_author'] = (name == sub_name) data['flair'] = flair @@ -113,7 +113,7 @@ class Content(object): """ reddit_link = re.compile( - 'https?://(www\.)?(np\.)?redd(it\.com|\.it)/r/.*') + r'https?://(www\.)?(np\.)?redd(it\.com|\.it)/r/.*') author = getattr(sub, 'author', '[deleted]') name = getattr(author, 'name', '[deleted]') flair = getattr(sub, 'link_flair_text', '') @@ -124,8 +124,8 @@ class Content(object): data['title'] = sub.title data['text'] = sub.selftext data['created'] = cls.humanize_timestamp(sub.created_utc) - data['comments'] = '{} comments'.format(sub.num_comments) - data['score'] = '{} pts'.format(sub.score) + data['comments'] = '{0} comments'.format(sub.num_comments) + data['score'] = '{0} pts'.format(sub.score) data['author'] = name data['permalink'] = sub.permalink data['subreddit'] = six.text_type(sub.subreddit) @@ -137,17 +137,17 @@ class Content(object): data['index'] = None # This is filled in later by the method caller if data['flair'] and not data['flair'].startswith('['): - data['flair'] = u'[{}]'.format(data['flair'].strip()) + data['flair'] = u'[{0}]'.format(data['flair'].strip()) url_full = data['url_full'] if data['permalink'].split('/r/')[-1] == url_full.split('/r/')[-1]: data['url_type'] = 'selfpost' - data['url'] = 'self.{}'.format(data['subreddit']) + data['url'] = 'self.{0}'.format(data['subreddit']) elif reddit_link.match(url_full): data['url_type'] = 'x-post' # Strip the subreddit name from the permalink to avoid having # submission.subreddit.url make a separate API call - data['url'] = 'self.{}'.format(url_full.split('/')[4]) + data['url'] = 'self.{0}'.format(url_full.split('/')[4]) else: data['url_type'] = 'external' data['url'] = url_full @@ -298,7 +298,7 @@ class SubmissionContent(Content): comment['cache'] = cache comment['count'] = count comment['level'] = data['level'] - comment['body'] = 'Hidden'.format(count) + comment['body'] = 'Hidden' self._comment_data[index:index + len(cache)] = [comment] elif data['type'] == 'HiddenComment': @@ -354,7 +354,7 @@ class SubredditContent(Content): if '/' in name: name, name_order = name.split('/') order = order or name_order - display_name = '/r/{}'.format(name) + display_name = '/r/{0}'.format(name) if order not in ['hot', 'top', 'rising', 'new', 'controversial', None]: raise exceptions.SubredditError('Unrecognized order "%s"' % order) diff --git a/rtv/oauth.py b/rtv/oauth.py index 03de25e..f1b006b 100644 --- a/rtv/oauth.py +++ b/rtv/oauth.py @@ -4,8 +4,8 @@ from __future__ import unicode_literals import time import uuid -from tornado import gen, ioloop, web, httpserver from concurrent.futures import ThreadPoolExecutor +from tornado import gen, ioloop, web, httpserver class OAuthHandler(web.RequestHandler): diff --git a/rtv/page.py b/rtv/page.py index 5039710..30e0fec 100644 --- a/rtv/page.py +++ b/rtv/page.py @@ -46,11 +46,10 @@ class Page(object): self._content_window = None self._subwindows = None - def refresh_content(self, order=None): + def refresh_content(self, order=None, name=None): raise NotImplementedError - @staticmethod - def _draw_item(window, data, inverted): + def _draw_item(self, window, data, inverted): raise NotImplementedError def loop(self): diff --git a/rtv/submission.py b/rtv/submission.py index 31b06c7..20f77d9 100644 --- a/rtv/submission.py +++ b/rtv/submission.py @@ -48,11 +48,11 @@ class SubmissionPage(Page): self.active = False @SubmissionController.register(curses.KEY_F5, 'r') - def refresh_content(self, order=None): + def refresh_content(self, order=None, name=None): "Re-download comments and reset the page index" order = order or self.content.order - url = self.content.name + url = name or self.content.name with self.term.loader(): self.content = SubmissionContent.from_url( @@ -124,18 +124,18 @@ class SubmissionPage(Page): else: self.term.flash() - def _draw_item(self, win, data, inverted=False): + def _draw_item(self, win, data, inverted): if data['type'] == 'MoreComments': return self._draw_more_comments(win, data) elif data['type'] == 'HiddenComment': return self._draw_more_comments(win, data) elif data['type'] == 'Comment': - return self._draw_comment(win, data, inverted=inverted) + return self._draw_comment(win, data, inverted) else: return self._draw_submission(win, data) - def _draw_comment(self, win, data, inverted=False): + def _draw_comment(self, win, data, inverted): n_rows, n_cols = win.getmaxyx() n_cols -= 1 @@ -172,9 +172,9 @@ class SubmissionPage(Page): attr = Color.get_level(data['level']) x = 0 for y in range(n_rows): - self.term.addch(win, y, x, curses.ACS_VLINE, attr) + self.term.addch(win, y, x, self.term.vline, attr) - return attr | curses.ACS_VLINE + return attr | self.term.vline def _draw_more_comments(self, win, data): @@ -185,9 +185,9 @@ class SubmissionPage(Page): self.term.add_line(win, ' [{count}]'.format(**data), attr=curses.A_BOLD) attr = Color.get_level(data['level']) - self.term.addch(win, 0, 0, curses.ACS_VLINE, attr) + self.term.addch(win, 0, 0, self.term.vline, attr) - return attr | curses.ACS_VLINE + return attr | self.term.vline def _draw_submission(self, win, data): diff --git a/rtv/subreddit.py b/rtv/subreddit.py index c2fbe47..bffa4f8 100644 --- a/rtv/subreddit.py +++ b/rtv/subreddit.py @@ -32,11 +32,11 @@ class SubredditPage(Page): self.nav = Navigator(self.content.get) @SubredditController.register(curses.KEY_F5, 'r') - def refresh_content(self, name=None, order=None): + def refresh_content(self, order=None, name=None): "Re-download all submissions and reset the page index" - name = name or self.content.name order = order or self.content.order + name = name or self.content.name # Hack to allow an order specified in the name by prompt_subreddit() to # override the current default @@ -71,7 +71,7 @@ class SubredditPage(Page): name = self.term.prompt_input('Enter Subreddit: /r/') if name is not None: - self.refresh_content(name=name, order='ignore') + self.refresh_content(order='ignore', name=name) @SubredditController.register(curses.KEY_RIGHT, 'l') def open_submission(self, url=None): @@ -161,10 +161,10 @@ class SubredditPage(Page): # When the user has chosen a subreddit in the subscriptions list, # refresh content with the selected subreddit if page.subreddit_data is not None: - self.refresh_content(name=page.subreddit_data['name'], - order='ignore') + self.refresh_content(order='ignore', + name=page.subreddit_data['name']) - def _draw_item(self, win, data, inverted=False): + def _draw_item(self, win, data, inverted): n_rows, n_cols = win.getmaxyx() n_cols -= 1 # Leave space for the cursor in the first column diff --git a/rtv/subscription.py b/rtv/subscription.py index 187c7a2..0329126 100644 --- a/rtv/subscription.py +++ b/rtv/subscription.py @@ -24,7 +24,7 @@ class SubscriptionPage(Page): self.subreddit_data = None @SubscriptionController.register(curses.KEY_F5, 'r') - def refresh_content(self, order=None): + def refresh_content(self, order=None, name=None): "Re-download all subscriptions and reset the page index" # reddit.get_my_subreddits() does not support sorting by order diff --git a/rtv/terminal.py b/rtv/terminal.py index a20cdd3..f440a28 100644 --- a/rtv/terminal.py +++ b/rtv/terminal.py @@ -60,6 +60,10 @@ class Terminal(object): attr = curses.A_BOLD | Color.YELLOW return symbol, attr + @property + def vline(self): + return getattr(curses, 'ACS_VLINE', ord('|')) + @property def display(self): """ @@ -229,7 +233,7 @@ class Terminal(object): n_rows, n_cols = self.stdscr.getmaxyx() - box_width = max(map(len, message)) + 2 + box_width = max(len(m) for m in message) + 2 box_height = len(message) + 2 # Cut off the lines of the message that don't fit on the screen @@ -291,9 +295,9 @@ class Terminal(object): if self.display: command = "import webbrowser; webbrowser.open_new_tab('%s')" % url args = [sys.executable, '-c', command] - null = open(os.devnull, 'ab+', 0) - p = subprocess.Popen(args, stdout=null, stderr=null) - with self.loader(message='Opening page in a new window'): + with self.loader(message='Opening page in a new window'), \ + open(os.devnull, 'ab+', 0) as null: + p = subprocess.Popen(args, stdout=null, stderr=null) # Give the browser 5 seconds to open a new tab. Because the # display is set, calling webbrowser should be non-blocking. # If it blocks or returns an error, something went wrong. diff --git a/tests/test_subreddit.py b/tests/test_subreddit.py index b4b3203..4777f9b 100644 --- a/tests/test_subreddit.py +++ b/tests/test_subreddit.py @@ -46,7 +46,7 @@ def test_subreddit_refresh(subreddit_page, terminal): assert terminal.loader.exception is None # Refresh with the order in the name - subreddit_page.refresh_content(name='/r/front/hot', order='ignore') + subreddit_page.refresh_content(order='ignore', name='/r/front/hot') assert subreddit_page.content.order == 'hot' assert subreddit_page.content.name == '/r/front' assert terminal.loader.exception is None @@ -132,13 +132,13 @@ def test_subreddit_post(subreddit_page, terminal, reddit, refresh_token): subreddit_page.oauth.authorize() # Post a submission to an invalid subreddit - subreddit_page.refresh_content('front') + subreddit_page.refresh_content(name='front') subreddit_page.controller.trigger('c') text = "Can't post to /r/front".encode('utf-8') terminal.stdscr.subwin.addstr.assert_called_with(1, 1, text) # Post a submission with a title but with no body - subreddit_page.refresh_content('python') + subreddit_page.refresh_content(name='python') with mock.patch.object(terminal, 'open_editor'): terminal.open_editor.return_value = 'title' subreddit_page.controller.trigger('c') diff --git a/tox.ini b/tox.ini index 837a630..fbe4662 100644 --- a/tox.ini +++ b/tox.ini @@ -4,8 +4,10 @@ envlist = py27,py34 [testenv] deps = pytest + pylint mock # Waiting for vcrpy to release https://github.com/kevin1024/vcrpy/pull/196 git+https://github.com/kevin1024/vcrpy.git commands = + pylint --rcfile .pylintrc rtv/ -E py.test -v {posargs}