8000 Add pyroject.toml with mypy section and type annotations by KotlinIsland · Pull Request #4043 · robotframework/robotframework · GitHub
[go: up one dir, main page]

Skip to content

Add pyroject.toml with mypy section and type annotations #4043

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions .github/workflows/mypy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Unit tests

on:
push:
branches:
- main
- master
paths:
- '.github/workflows/**'
- 'src/**'
- 'utest/**'
- '!**/*.rst'


jobs:
test_using_builtin_python:

strategy:
fail-fast: false
matrix:
os: [ 'ubuntu-latest', 'windows-latest' ]
python-version: [ '3.6', '3.7', '3.8', '3.9', 'pypy3' ]
exclude:
- os: windows-latest
python-version: 'pypy3'

runs-on: ${{ matrix.os }}

name: Python ${{ matrix.python-version }} on ${{ matrix.os }}
steps:
- uses: actions/checkout@v2

- name: Setup python ${{ matrix.python-version }}
uses: actions/setup-python@v2.2.2
with:
python-version: ${{ matrix.python-version }}
architecture: 'x64'

- name: Run mypy
run: |
python -m pip install mypy
python -m mypy --install-types --non-interactive src
21 changes: 21 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[tool.mypy]
allow_redefinition = true
show_error_codes = true
warn_redundant_casts = true

[[tool.mypy.overrides]]
module = "robot.*"

[[tool.mypy.overrides]]
module = "robot.model.testcase"
strict_equality = true
warn_unused_ignores = true
disallow_any_generics = true
disallow_untyped_defs = true

[[tool.mypy.overrides]]
module = "robot.api.*"
strict_equality = true
warn_unused_ignores = true
disallow_any_generics = true
disallow_untyped_defs = true
2 changes: 1 addition & 1 deletion src/robot/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
# Allows running as a script. __name__ check needed with multiprocessing:
# https://github.com/robotframework/robotframework/issues/1137
if 'robot' not in sys.modules and __name__ == '__main__':
import pythonpathsetter
import pythonpathsetter # type: ignore[import]

from robot import run_cli

Expand Down
63 changes: 45 additions & 18 deletions src/robot/api/deco.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,17 @@

import inspect

from typing import overload, cast, Callable, TypeVar, Union, List, Any, TYPE_CHECKING, Mapping

def not_keyword(func):
if TYPE_CHECKING:
from typing_extensions import Literal

Fn = TypeVar("Fn", bound=Callable[..., Any])
Cls = TypeVar("Cls", bound=type)
_Types = Union[Mapping[str, type], List[type], None]


def not_keyword(func: Fn) -> Fn:
"""Decorator to disable exposing functions or methods as keywords.

Examples::
Expand All @@ -34,15 +43,23 @@ def exposed_as_keyword():

New in Robot Framework 3.2.
"""
func.robot_not_keyword = True
func.robot_not_keyword = True # type: ignore[attr-defined]
return func


not_keyword.robot_not_keyword = True
not_keyword.robot_not_keyword = True # type: ignore[attr-defined]


@overload
def keyword(name: Fn) -> Fn: ...


@overload
def keyword(name: str = None, tags: List[str] = ..., types: _Types = ...) -> Callable[[Fn], Fn]: ...


@not_keyword
def keyword(name=None, tags=(), types=()):
def keyword(name: object = None, tags: object = (), types: object = ()) -> object:
"""Decorator to set custom name, tags and argument types to keywords.

This decorator creates ``robot_name``, ``robot_tags`` and ``robot_types``
Expand Down Expand Up @@ -85,20 +102,30 @@ def no_conversion(length, case_insensitive=False):
# ...
"""
if inspect.isroutine(name):
return keyword()(name)
return keyword()(name) # type: ignore[type-var, return-value]

def decorator(func):
func.robot_name = name
func.robot_tags = tags
func.robot_types = types
def decorator(func: Fn) -> Fn:
func.robot_name = name # type:ignore[attr-defined]
func.robot_tags = tags # type:ignore[attr-defined]
func.robot_types = types # type:ignore[attr-defined]
return func

return decorator


@overload
def library(scope: Cls) -> Cls: ...


@overload
def library(scope: 'Literal["GLOBAL", "SUITE", "TEST", None]' = None,
version: str = None, doc_format: str = None, listener: str = None,
auto_keywords: bool = False) -> Callable[[Cls], Cls]: ...


@not_keyword
def library(scope=None, version=None, doc_format=None, listener=None,
auto_keywords=False):
def library(scope: object = None, version: str = None, doc_format: str = None, listener: str = None,
auto_keywords: bool = False) -> object:
"""Class decorator to control keyword discovery and other library settings.

By default disables automatic keyword detection by setting class attribute
Expand Down Expand Up @@ -134,18 +161,18 @@ class LibraryConfiguration:
The ``@library`` decorator is new in Robot Framework 3.2.
"""
if inspect.isclass(scope):
return library()(scope)
return library()(cast(type, scope))

def decorator(cls):
def decorator(cls: Cls) -> Cls:
if scope is not None:
cls.ROBOT_LIBRARY_SCOPE = scope
cls.ROBOT_LIBRARY_SCOPE = scope # type:ignore[attr-defined]
if version is not None:
cls.ROBOT_LIBRARY_VERSION = version
cls.ROBOT_LIBRARY_VERSION = version # type:ignore[attr-defined]
if doc_format is not None:
cls.ROBOT_LIBRARY_DOC_FORMAT = doc_format
cls.ROBOT_LIBRARY_DOC_FORMAT = doc_format # type:ignore[attr-defined]
if listener is not None:
cls.ROBOT_LIBRARY_LISTENER = listener
cls.ROBOT_AUTO_KEYWORDS = auto_keywords
cls.ROBOT_LIBRARY_LISTENER = listener # type:ignore[attr-defined]
cls.ROBOT_AUTO_KEYWORDS = auto_keywords # type:ignore[attr-defined]
return cls

return decorator
6 changes: 3 additions & 3 deletions src/robot/api/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class Failure(AssertionError):
"""
ROBOT_SUPPRESS_NAME = True

def __init__(self, message, html=False):
def __init__(self, message: str, html: bool=False) -> None:
"""
:param message: Exception message.
:param html: When ``True``, message is considered to be HTML and not escaped.
Expand All @@ -57,7 +57,7 @@ class Error(RuntimeError):
"""
ROBOT_SUPPRESS_NAME = True

def __init__(self, message, html=False):
def __init__(self, message: str, html: bool=False) -> None:
"""
:param message: Exception message.
:param html: When ``True``, message is considered to be HTML and not escaped.
Expand All @@ -76,7 +76,7 @@ class SkipExecution(Exception):
ROBOT_SKIP_EXECUTION = True
ROBOT_SUPPRESS_NAME = True

def __init__(self, message, html=False):
def __init__(self, message: str, html: bool=False) -> None:
"""
:param message: Exception message.
:param html: When ``True``, message is considered to be HTML and not escaped.
Expand Down
26 changes: 16 additions & 10 deletions src/robot/api/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,16 @@ def my_keyword(arg):
"""

import logging

from typing import TYPE_CHECKING
from robot.output import librarylogger
from robot.running.context import EXECUTION_CONTEXTS


def write(msg, level='INFO', html=False):
if TYPE_CHECKING:
from typing_extensions import Literal


def write(msg: str, level: 'Literal["TRACE", "DEBUG", "INFO", "WARN", "ERROR"]' = 'INFO', html: bool = False) -> None:
"""Writes the message to the log file using the given level.

Valid log levels are ``TRACE``, ``DEBUG``, ``INFO`` (default), ``WARN``, and
Expand All @@ -86,26 +90,28 @@ def write(msg, level='INFO', html=False):
librarylogger.write(msg, level, html)
else:
logger = logging.getLogger("RobotFramework")
level = {'TRACE': logging.DEBUG // 2,
level = {'TRACE': logging.DEBUG // 2, # type: ignore[assignment]
'DEBUG': logging.DEBUG,
'INFO': logging.INFO,
'HTML': logging.INFO,
'WARN': logging.WARN,
'ERROR': logging.ERROR}[level]
logger.log(level, msg)
logger.log(level, msg) # type: ignore[arg-type]




def trace(msg, html=False):
def trace(msg: str, html: bool=False) -> None:
"""Writes the message to the log file using the ``TRACE`` level."""
write(msg, 'TRACE', html)


def debug(msg, html=False):
def debug(msg: str, html: bool=False) -> None:
"""Writes the message to the log file using the ``DEBUG`` level."""
write(msg, 'DEBUG', html)


def info(msg, html=False, also_console=False):
def info(msg: str, html: bool=False, also_console: bool=False) -> None:
"""Writes the message to the log file using the ``INFO`` level.

If ``also_console`` argument is set to ``True``, the message is
Expand All @@ -116,18 +122,18 @@ def info(msg, html=False, also_console=False):
console(msg)


def warn(msg, html=False):
def warn(msg: str, html: bool=False) -> None:
"""Writes the message to the log file using the ``WARN`` level."""
write(msg, 'WARN', html)


def error(msg, html=False):
def error(msg: str, html: bool=False) -> None:
"""Writes the message to the log file using the ``ERROR`` level.
"""
write(msg, 'ERROR', html)


def console(msg, newline=True, stream='stdout'):
def console(msg: str, newline: bool=True, stream: str='stdout') -> None:
"""Writes the message to the console.

If the ``newline`` argument is ``True``, a newline character is
Expand Down
4 changes: 3 additions & 1 deletion src/robot/conf/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import random
import sys
import time
from typing import Dict, Tuple

from robot.errors import DataError, FrameworkError
from robot.output import LOGGER, loggerhelper
Expand All @@ -30,7 +31,8 @@


class _BaseSettings:
_cli_opts = {'RPA' : ('rpa', None),
_cli_opts: Dict[str, Tuple[str, object]] = {
'RPA' : ('rpa', None),
'Name' : ('name', None),
'Doc' : ('doc', None),
'Metadata' : ('metadata', []),
Expand Down
3 changes: 2 additions & 1 deletion src/robot/htmldata/htmlfilewriter.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

import os.path
import re
from typing import Optional

from robot.utils import HtmlWriter
from robot.version import get_full_version
Expand Down Expand Up @@ -46,7 +47,7 @@ def _get_writers(self, base_dir):


class _Writer:
_handles_line = None
_handles_line: Optional[str] = None

def handles(self, line):
return line.startswith(self._handles_line)
Expand Down
4 changes: 3 additions & 1 deletion src/robot/htmldata/jsonwriter.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
# 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 typing import Optional, Union, Tuple


class JsonWriter:

Expand Down Expand Up @@ -55,7 +57,7 @@ def dump(self, data, mapping=None):


class _Dumper:
_handled_types = None
_handled_types: Union[type, Tuple[type, ...], None] = None

def __init__(self, jsondumper):
self._dump = jsondumper.dump
Expand Down
2 changes: 1 addition & 1 deletion src/robot/htmldata/testdata/create_libdoc_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@

with open(OUTPUT, 'w') as output:
libdoc = LibraryDocumentation(INPUT)
LibdocModelWriter(output, libdoc).write_data()
LibdocModelWriter(output, libdoc).write_data() # type: ignore[attr-defined]

print(OUTPUT)
2 changes: 1 addition & 1 deletion src/robot/libdoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
# Allows running as a script. __name__ check needed with multiprocessing:
# https://github.com/robotframework/robotframework/issues/1137
if 'robot' not in sys.modules and __name__ == '__main__':
import pythonpathsetter
import pythonpathsetter # type: ignore[import]

from robot.utils import Application, seq2str
from robot.errors import DataError
Expand Down
2 changes: 1 addition & 1 deletion src/robot/libdocpkg/htmlutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

import re
try:
from urllib import quote
from urllib import quote # type: ignore[attr-defined]
except ImportError:
from urllib.parse import quote

Expand Down
2 changes: 1 addition & 1 deletion src/robot/libdocpkg/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ def _get_shortdoc(self):
doc = HtmlToText().get_shortdoc_from_html(doc)
return ' '.join(getshortdoc(doc).splitlines())

@shortdoc.setter
@shortdoc.setter # type: ignore[no-redef,attr-defined]
def shortdoc(self, shortdoc):
self._shortdoc = shortdoc

Expand Down
5 changes: 3 additions & 2 deletions src/robot/libraries/Remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import socket
import sys
import xmlrpc.client
from typing import Type
from xml.parsers.expat import ExpatError

from robot.errors import RemoteError
Expand Down Expand Up @@ -198,7 +199,7 @@ def __init__(self, uri, timeout=None):
self.uri = uri
self.timeout = timeout

@property
@property # type: ignore[misc]
@contextmanager
def _server(self):
if self.uri.startswith('https://'):
Expand Down Expand Up @@ -259,7 +260,7 @@ def run_keyword(self, name, args, kwargs):
# http://stackoverflow.com/questions/2425799/timeout-for-xmlrpclib-client-requests

class TimeoutHTTPTransport(xmlrpc.client.Transport):
_connection_class = http.client.HTTPConnection
_connection_class: Type[http.client.HTTPConnection] = http.client.HTTPConnection

def __init__(self, use_datetime=0, timeout=None):
xmlrpc.client.Transport.__init__(self, use_datetime)
Expand Down
Loading
0