Add mypy checks

Add the mypy stubs and the mypy configuration to be able to run mypy on
qtile, and make the various changes to qtile to get the mypy checks to
run successfully.  Add the mypy check to Travis.
master
Sean Vig 2018-03-25 00:55:46 -04:00
parent cf696ae80d
commit ec6a6ba893
60 changed files with 243 additions and 61 deletions

1
.gitignore vendored
View File

@ -38,3 +38,4 @@ debian/*debhelper
.tox/
.cache/
.pytest_cache/
.mypy_cache/

View File

@ -28,6 +28,8 @@ matrix:
env: TOXENV=pep8
- python: 2.7
env: TOXENV=pep8
- python: 3.6
env: TOXENV=mypy
allow_failures:
- python: nightly

View File

@ -21,16 +21,6 @@
from __future__ import absolute_import
import six
moves = [
six.MovedAttribute("getoutput", "commands", "subprocess"),
six.MovedModule("asyncio", "trollius", "asyncio"),
]
for m in moves:
six.add_move(m)
# Keep supporting the deprecated misspelled subpackage "extention"
# TODO: Remove in the future
from . import extension as extention # noqa

View File

@ -0,0 +1,26 @@
# Copyright (c) 2018 Sean Vig
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import six
if six.PY2:
import trollius as asyncio
else:
import asyncio # noqa: F401

View File

@ -18,9 +18,14 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
try:
from typing import Dict # noqa: F401
except ImportError:
pass
class Configurable(object):
global_defaults = {}
global_defaults = {} # type: Dict
def __init__(self, **config):
self._variable_defaults = {}

View File

@ -22,11 +22,16 @@ import shlex
from subprocess import Popen, PIPE
from .. import configurable
try:
from typing import Any, List, Tuple # noqa: F401
except ImportError:
pass
class _Extension(configurable.Configurable):
"""Base Extension class"""
installed_extensions = []
installed_extensions = [] # type: List
defaults = [
("font", "sans", "defines the font name to be used"),
@ -68,7 +73,7 @@ class RunCommand(_Extension):
# modified it, all the other objects would see the modified list;
# use a string or a tuple instead, which are immutable
("command", None, "the command to be launched (string or list with arguments)"),
]
] # type: List[Tuple[str, Any, str]]
def __init__(self, **config):
_Extension.__init__(self, **config)

View File

@ -33,8 +33,14 @@
from .log_utils import logger
from . import utils
subscriptions = {}
SKIPLOG = set()
try:
from typing import Dict, Set # noqa: F401
except ImportError:
pass
subscriptions = {} # type: Dict
SKIPLOG = set() # type: Set
qtile = None

View File

@ -31,8 +31,7 @@ import struct
import fcntl
import json
from six.moves import asyncio
from .asyncio_compat import asyncio
from .log_utils import logger
HDRLEN = 4

View File

@ -25,6 +25,11 @@ from abc import ABCMeta, abstractmethod
from .. import command, configurable
try:
from typing import Any, List, Tuple # noqa: F401
except ImportError:
pass
@six.add_metaclass(ABCMeta)
class Layout(command.CommandObject, configurable.Configurable):
@ -38,7 +43,7 @@ class Layout(command.CommandObject, configurable.Configurable):
None,
"The name of this layout"
" (usually the class' name in lowercase, e.g. 'max')"
)]
)] # type: List[Tuple[str, Any, str]]
def __init__(self, **config):
# name is a little odd; we can't resolve it until the class is defined

View File

@ -41,8 +41,7 @@ import xcffib.xproto
import six
import warnings
from six.moves import asyncio
from .asyncio_compat import asyncio
from .config import Drag, Click, Screen, Match, Rule
from .config import ScratchPad as ScratchPadConfig
from .group import _Group

View File

@ -28,6 +28,11 @@ from libqtile.config import Key, Screen, Group, Drag, Click
from libqtile.command import lazy
from libqtile import layout, bar, widget
try:
from typing import List # noqa: F401
except ImportError:
pass
mod = "mod4"
keys = [
@ -110,7 +115,7 @@ mouse = [
]
dgroups_key_binder = None
dgroups_app_rules = []
dgroups_app_rules = [] # type: List
main = None
follow_mouse_focus = True
bring_front_click = False

View File

@ -23,6 +23,13 @@
from ..utils import safe_import as safe_import_
from .import_error import make_error
from .clock import Clock # noqa: F401
from .groupbox import AGroupBox, GroupBox # noqa: F401
from .prompt import Prompt # noqa: F401
from .systray import Systray # noqa: F401
from .textbox import TextBox # noqa: F401
from .windowname import WindowName # noqa: F401
def safe_import(module_name, class_name):
safe_import_((".widget", module_name), class_name, globals(),
@ -31,25 +38,19 @@ def safe_import(module_name, class_name):
safe_import("backlight", "Backlight")
safe_import("battery", ["Battery", "BatteryIcon"])
safe_import("clock", "Clock")
safe_import("currentlayout", ["CurrentLayout", "CurrentLayoutIcon"])
safe_import("currentscreen", "CurrentScreen")
safe_import("debuginfo", "DebugInfo")
safe_import("graph", ["CPUGraph", "MemoryGraph", "SwapGraph", "NetGraph",
"HDDGraph", "HDDBusyGraph"])
safe_import("groupbox", ["AGroupBox", "GroupBox"])
safe_import("maildir", "Maildir")
safe_import("notify", "Notify")
safe_import("prompt", "Prompt")
safe_import("sensors", "ThermalSensor")
safe_import("sep", "Sep")
safe_import("she", "She")
safe_import("spacer", "Spacer")
safe_import("systray", "Systray")
safe_import("textbox", "TextBox")
safe_import("generic_poll_text", ["GenPollText", "GenPollUrl"])
safe_import("volume", "Volume")
safe_import("windowname", "WindowName")
safe_import("windowtabs", "WindowTabs")
safe_import("keyboardlayout", "KeyboardLayout")
safe_import("df", "DF")

View File

@ -24,13 +24,18 @@
import os
from . import base
try:
from typing import Dict # noqa: F401
except ImportError:
pass
BACKLIGHT_DIR = '/sys/class/backlight'
class Backlight(base.InLoopPollText):
"""A simple widget to show the current brightness of a monitor"""
filenames = {}
filenames = {} # type: Dict
orientations = base.ORIENTATION_HORIZONTAL

View File

@ -36,6 +36,11 @@ import subprocess
import threading
import warnings
try:
from typing import Any, List, Tuple # noqa: F401
except ImportError:
pass
# Each widget class must define which bar orientation(s) it supports by setting
# these bits in an 'orientations' class attribute. Simply having the attribute
@ -94,7 +99,7 @@ class _Widget(command.CommandObject, configurable.Configurable):
orientations = ORIENTATION_BOTH
offsetx = None
offsety = None
defaults = [("background", None, "Widget background color")]
defaults = [("background", None, "Widget background color")] # type: List[Tuple[str, Any, str]]
def __init__(self, length, **config):
"""
@ -292,7 +297,7 @@ class _TextBox(_Widget):
"font shadow color, default is None(no shadow)"
),
("markup", False, "Whether or not to use pango markup"),
]
] # type: List[Tuple[str, Any, str]]
def __init__(self, text=" ", width=bar.CALCULATED, **config):
self.layout = None
@ -311,10 +316,6 @@ class _TextBox(_Widget):
if self.layout:
self.layout.text = value
@property
def font(self):
return self._font
@property
def foreground(self):
return self._foreground
@ -325,6 +326,10 @@ class _TextBox(_Widget):
if self.layout:
self.layout.colour = fg
@property
def font(self):
return self._font
@font.setter
def font(self, value):
self._font = value
@ -413,7 +418,7 @@ class InLoopPollText(_TextBox):
defaults = [
("update_interval", 600, "Update interval in seconds, if none, the "
"widget updates whenever the event loop is idle."),
]
] # type: List[Tuple[str, Any, str]]
def __init__(self, **config):
_TextBox.__init__(self, 'N/A', width=bar.CALCULATED, **config)
@ -494,7 +499,7 @@ class ThreadPoolText(_TextBox):
defaults = [
("update_interval", None, "Update interval in seconds, if none, the "
"widget updates whenever it's done'."),
]
] # type: List[Tuple[str, Any, str]]
def __init__(self, text, **config):
super(ThreadPoolText, self).__init__(text, width=bar.CALCULATED,
@ -556,7 +561,7 @@ class PaddingMixin(object):
("padding", 3, "Padding inside the box"),
("padding_x", None, "X Padding. Overrides 'padding' if set"),
("padding_y", None, "Y Padding. Overrides 'padding' if set"),
]
] # type: List[Tuple[str, Any, str]]
padding_x = configurable.ExtraFallback('padding_x', 'padding')
padding_y = configurable.ExtraFallback('padding_y', 'padding')
@ -574,7 +579,7 @@ class MarginMixin(object):
("margin", 3, "Margin inside the box"),
("margin_x", None, "X Margin. Overrides 'margin' if set"),
("margin_y", None, "Y Margin. Overrides 'margin' if set"),
]
] # type: List[Tuple[str, Any, str]]
margin_x = configurable.ExtraFallback('margin_x', 'margin')
margin_y = configurable.ExtraFallback('margin_y', 'margin')

View File

@ -37,6 +37,11 @@ from libqtile import bar
from libqtile.log_utils import logger
from . import base
try:
from typing import Dict # noqa: F401
except ImportError:
pass
BAT_DIR = '/sys/class/power_supply'
CHARGED = 'Full'
CHARGING = 'Charging'
@ -57,18 +62,19 @@ def default_icon_path():
return os.path.join(root, 'resources', 'battery-icons')
def _get_battery_name():
bats = [f for f in os.listdir(BAT_DIR) if f.startswith('BAT')]
if bats:
return bats[0]
else:
return 'BAT0'
class _Battery(base._TextBox):
"""Base battery class"""
filenames = {}
def _get_battery_name():
bats = [f for f in os.listdir(BAT_DIR) if f.startswith('BAT')]
if bats:
return bats[0]
else:
return 'BAT0'
filenames = {} # type: Dict
defaults = [
('battery_name', _get_battery_name(), 'ACPI name of a battery, usually BAT0'),

View File

@ -27,6 +27,8 @@ from . import base
from .generic_poll_text import GenPollUrl
import locale
_DEFAULT_CURRENCY = str(locale.localeconv()['int_curr_symbol'])
class BitcoinTicker(GenPollUrl):
"""
@ -46,7 +48,7 @@ class BitcoinTicker(GenPollUrl):
orientations = base.ORIENTATION_HORIZONTAL
defaults = [
('currency', locale.localeconv()['int_curr_symbol'].strip(),
('currency', _DEFAULT_CURRENCY.strip(),
'The currency the value that bitcoin is displayed in'),
]

View File

@ -35,12 +35,17 @@ import itertools
from .. import bar, hook
from . import base
try:
from typing import Any, List, Tuple # noqa: F401
except ImportError:
pass
class _GroupBase(base._TextBox, base.PaddingMixin, base.MarginMixin):
defaults = [
("borderwidth", 3, "Current group border width"),
("center_aligned", False, "center-aligned group box"),
]
] # type: List[Tuple[str, Any, str]]
def __init__(self, **config):
base._TextBox.__init__(self, width=bar.CALCULATED, **config)

View File

@ -48,8 +48,8 @@ try:
except ImportError:
import iwlib
def get_status(interface):
interface = iwlib.get_iwconfig(interface)
def get_status(interface_name):
interface = iwlib.get_iwconfig(interface_name)
if 'stats' not in interface:
return None, None
quality = interface['stats']['quality']

View File

@ -155,7 +155,7 @@ def _float_setter(attr):
class _Window(command.CommandObject):
_windowMask = None # override in child class
_windowMask = 0 # override in child class
def __init__(self, window, qtile):
self.window, self.qtile = window, qtile
@ -214,14 +214,22 @@ class _Window(command.CommandObject):
x = property(fset=_geometry_setter("x"), fget=_geometry_getter("x"))
y = property(fset=_geometry_setter("y"), fget=_geometry_getter("y"))
width = property(
fset=_geometry_setter("width"),
fget=_geometry_getter("width")
)
height = property(
fset=_geometry_setter("height"),
fget=_geometry_getter("height")
)
@property
def width(self):
return _geometry_getter("width")(self)
@width.setter
def width(self, value):
_geometry_setter("width")(self, value)
@property
def height(self):
return _geometry_getter("height")(self)
@height.setter
def height(self, value):
_geometry_setter("height")(self, value)
float_x = property(
fset=_float_setter("x"),
@ -582,7 +590,7 @@ class _Window(command.CommandObject):
self.qtile.root.set_property("_NET_ACTIVE_WINDOW", self.window.wid)
hook.fire("client_focus", self)
def _items(self, name, sel):
def _items(self, name):
return None
def _select(self, name, sel):

View File

@ -6,3 +6,19 @@ max-line-length = 120
python_files = test_*.py
testpaths = test
addopts = --verbose
[check-manifest]
ignore =
stubs**
[mypy]
mypy_path = stubs
python_version = 2.7
[mypy-libqtile/_ffi_*]
ignore_errors = True
[mypy-trollius]
ignore_missing_imports = True
[mypy-libqtile._ffi_pango]
ignore_missing_imports = True
[mypy-libqtile._ffi_xcursors]
ignore_missing_imports = True

View File

View File

View File

@ -0,0 +1,3 @@
from tempfile import TemporaryDirectory as _TemporaryDirectory
class TemporaryDirectory(_TemporaryDirectory): ...

View File

View File

@ -0,0 +1 @@
ffi = ...

9
stubs/cffi.pyi Normal file
View File

@ -0,0 +1,9 @@
from typing import List
__version_info__ = ... # type: List
class FFI:
def cdef(self, csource, override=False, packed=False): ...
def compile(self, tmpdir=".", verbose=0, target=None, debug=None): ...
def include(self, ffi_to_include): ...
def set_source(self, module_name, source, source_extension=".c", **kwargs): ...

9
stubs/curses.pyi Normal file
View File

@ -0,0 +1,9 @@
def init_pair(n, f, b): ...
A_BOLD = ...
A_REVERSE = ...
COLOR_GREEN = ...
COLOR_BLACK = ...

0
stubs/dbus/__init__.pyi Normal file
View File

View File

View File

@ -0,0 +1 @@
def DBusGMainLoop(set_as_default=False): ...

View File

@ -0,0 +1,7 @@
class BusName: ...
class Object: ...
def method(dbus_interface, in_signature=None, out_signature=None): ...
def signal(dbus_interface, signature=None): ...

0
stubs/gi/__init__.pyi Normal file
View File

View File

View File

View File

@ -0,0 +1 @@
class IPKernelApp: ...

View File

@ -0,0 +1 @@
class Kernel: ...

1
stubs/iwlib.pyi Normal file
View File

@ -0,0 +1 @@
def get_iwconfig(interface): ...

View File

View File

@ -0,0 +1 @@
def install_kernel_spec(source_dir, kernel_name=None, user=False, replace=False, prefix=None): ...

0
stubs/keyring.pyi Normal file
View File

1
stubs/mailbox.pyi Normal file
View File

@ -0,0 +1 @@
class Maildir: ...

5
stubs/mpd.pyi Normal file
View File

@ -0,0 +1,5 @@
class MPDClient: ...
class ConnectionError: ...
class CommandError: ...

3
stubs/pkg_resources.pyi Normal file
View File

@ -0,0 +1,3 @@
def require(*requirements): ...
class DistributionNotFound(Exception): ...

View File

View File

@ -0,0 +1,3 @@
class Wireless: ...
class Iwstats: ...

1
stubs/setproctitle.pyi Normal file
View File

@ -0,0 +1 @@
def setproctitle(title): ...

1
stubs/tracemalloc.pyi Normal file
View File

@ -0,0 +1 @@
class Snapshot: ...

View File

View File

@ -0,0 +1 @@
ffi = ...

0
stubs/xcffib/randr.pyi Normal file
View File

6
stubs/xcffib/xfixes.pyi Normal file
View File

@ -0,0 +1,6 @@
class SelectionEvent: ...
class SelectionEventMask:
SetSelectionOwner = ... # type: int
SelectionWindowDestroy = ... # type: int
SelectionClientClose = ... # type: int

View File

29
stubs/xcffib/xproto.pyi Normal file
View File

@ -0,0 +1,29 @@
class ClientMessageEvent: ...
class ClientMessageData: ...
class ConfigWindow: ...
class CW: ...
class EventMask:
KeyPress = ... # type: int
ButtonPress = ... # type: int
ButtonRelease = ... # type: int
EnterWindow = ... # type: int
Exposure = ... # type: int
StructureNotify = ... # type: int
FocusChange = ... # type: int
PropertyChange = ... # type: int
class SetMode: ...
class StackMode: ...
class WindowClass: ...
class AccessError: ...
class DrawableError: ...
class WindowError: ...

View File

@ -0,0 +1 @@
def getIconPath(iconname): ...

0
stubs/xdg/__init__.pyi Normal file
View File

0
stubs/xml/__init__.pyi Normal file
View File

View File

View File

@ -0,0 +1 @@
def parseString(string, parser=None): ...

0
stubs/xmltodict.pyi Normal file
View File

View File

@ -12,6 +12,7 @@ envlist =
py-nightly
docs,
pep8,
mypy,
packaging
[testenv]
@ -47,6 +48,10 @@ commands =
deps = flake8
commands = flake8 {toxinidir}/libqtile {toxinidir}/bin/
[testenv:mypy]
deps = mypy
commands = mypy -p libqtile
[testenv:docs]
deps = -r{toxinidir}/docs/requirements.txt
commands = sphinx-build -W -b html -d {envtmpdir}/doctrees docs docs/_build/html