diff --git a/.travis.yml b/.travis.yml index da0c1df..bd43186 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,13 +3,13 @@ sudo: required services: - docker python: -- '2.7' - '3.5' - '3.6' install: -- pip install mypy +- pip install mypy pycodestyle - pip install . script: +- pycodestyle src - mypy src --ignore-missing-imports notifications: email: false diff --git a/setup.py b/setup.py index 8c16bf0..ced866f 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', ], - python_requires='>=2.7', + python_requires='>=3.5', install_requires=[ 'requests>=2.0.0', 'typing>=0.4.1' diff --git a/src/rooibos/__init__.py b/src/rooibos/__init__.py index 477b241..2c12590 100644 --- a/src/rooibos/__init__.py +++ b/src/rooibos/__init__.py @@ -1,100 +1,87 @@ -from typing import Dict, Tuple, Iterator, List, Any -from contextlib import contextmanager -from tempfile import TemporaryFile +# -*- coding: utf-8 -*- +__all__ = ( + 'Location', + 'LocationRange', + 'BoundTerm', + 'Environment', + 'Match', + 'Client', + 'ephemeral_server' +) + +from typing import Dict, Tuple, Iterator, List, Any, Optional +from urllib.parse import urljoin, urlparse from timeit import default_timer as timer +from contextlib import contextmanager import time import os import subprocess import signal import logging -try: - from urllib.parse import urljoin, urlparse -except ImportError: - from urlparse import urljoin, urlparse - import requests from .version import __version__ from .exceptions import * -logging.getLogger(__name__).addHandler(logging.NullHandler()) - -__all__ = [ - 'Location', - 'LocationRange', - 'BoundTerm', - 'Environment', - 'Match', - 'Client', - 'ephemeral_server' -] +logger = logging.getLogger(__name__) # type: logging.Logger +logger.setLevel(logging.DEBUG) -class Location(object): +class Location: """ Represents the location of a single character within a source text by its zero-indexed line and column numbers. """ @staticmethod - def from_string(s): - # type: (str) -> Location + def from_string(s: str) -> 'Location': s_line, _, s_col = s.partition(":") line = int(s_line) col = int(s_col) return Location(line, col) - def __init__(self, line, col): - # type: (int, int) -> None - assert line > 0, "expected one-indexed line number greater than zero" - assert col >= 0, "expected one-indexed column number greater or equal to zero" + def __init__(self, line: int, col: int) -> None: + assert line > 0, \ + 'expected one-indexed line number greater than zero' + assert col >= 0, \ + 'expected one-indexed column number greater or equal to zero' self.__line = line self.__col = col - def __str__(self): - # type: () -> str + def __str__(self) -> str: """ Describes this location as a string of the form `line:col`, where `line and `col` are one-indexed line and column numbers. """ return "{}:{}".format(self.line, self.col) - def __repr__(self): - # type: () -> str + def __repr__(self) -> str: return "rooibos.Location({})".format(self.__str__()) @property - def line(self): - # type: () -> int - """ - The one-indexed line number for this location. - """ + def line(self) -> int: + """The one-indexed line number for this location.""" return self.__line @property - def col(self): - # type: () -> int - """ - The one-indexed column number for this location. - """ + def col(self) -> int: + """The one-indexed column number for this location.""" return self.__col -class LocationRange(object): +class LocationRange: """ Represents a contiguous range of locations within a given source text as a (non-inclusive) range of character positions. """ @staticmethod - def from_string(s): - # type: (str) -> LocationRange + def from_string(s: str) -> 'LocationRange': s_start, _, s_end = s.partition("::") loc_start = Location.from_string(s_start) loc_end = Location.from_string(s_end) return LocationRange(loc_start, loc_end) - def __init__(self, start, stop): - # type: (Location, Location) -> None + def __init__(self, start: Location, stop: Location) -> None: """ Constructs a (non-inclusive) location range from a start and stop location. @@ -106,57 +93,44 @@ def __init__(self, start, stop): self.__start = start self.__stop = stop - def __str__(self): - # type: () -> str + def __str__(self) -> str: """ Describes this location range as a string of the form `start::stop`, where `start` and `stop` are string-based descriptions of the positions - of the first and last characters within this source range, respectively. + of the first and last characters within this source range, + respectively. """ return "{}::{}".format(self.start, self.stop) - def __repr__(self): - # type: () -> str + def __repr__(self) -> str: return "rooibos.LocationRange({})".format(self.__str__()) @property - def start(self): - # type: () -> Location - """ - The position at which this range begins. - """ + def start(self) -> Location: + """The position at which this range begins.""" return self.__start @property - def stop(self): - # type: () -> Location - """ - The position at which this range ends, inclusive. - """ + def stop(self) -> Location: + """The position at which this range ends, inclusive.""" return self.__stop -class BoundTerm(object): - """ - Represents a binding of a named term to a fragment of source code. - """ +class BoundTerm: + """Represents a binding of a named term to a fragment of source code.""" @staticmethod - def from_dict(d): - # type: (Dict[str, Any]) -> BoundTerm - """ - Constructs a bound term from a dictionary-based description. - """ + def from_dict(d: Dict[str, Any]) -> 'BoundTerm': + """Constructs a bound term from a dictionary-based description.""" return BoundTerm(term=d['term'], location=LocationRange.from_string(d['location']), fragment=d['content']) def __init__(self, - term, # type: str, - location, # type: LocationRange, - fragment, # type: str - ): # type: (...) -> None - """ - Constructs a new bound term. + term: str, + location: LocationRange, + fragment: str + ) -> None: + """Constructs a new bound term. Parameters: term: the name of the term. @@ -167,65 +141,47 @@ def __init__(self, self.__location = location self.__fragment = fragment - def __repr__(self): - # type: () -> str + def __repr__(self) -> str: s = "rooibos.BoundTerm({}, {}, {})" return s.format(self.term, str(self.location), self.fragment) @property - def term(self): - # type: () -> str - """ - The name of the term. - """ + def term(self) -> str: + """The name of the term.""" return self.__term @property - def location(self): - # type: () -> LocationRange - """ - The location range covered by this term. - """ + def location(self) -> LocationRange: + """The location range covered by this term.""" return self.__location @property - def fragment(self): - # type: () -> str - """ - The source code fragment to which this term is bound, given as a - string. - """ + def fragment(self) -> str: + """The source code fragment to which this term is bound.""" return self.__fragment -class Environment(object): +class Environment: @staticmethod - def from_dict(d): - # type: (Dict[str, Any]) -> Environment - return Environment([BoundTerm.from_dict(bt) for bt in d]) + def from_dict(d: Dict[str, Any]) -> 'Environment': + return Environment([BoundTerm.from_dict(bt) for bt in d.values()]) - def __init__(self, bindings): - # type: (List[BoundTerm]) -> None + def __init__(self, bindings: List[BoundTerm]) -> None: self.__bindings = {b.term: b for b in bindings} - def __repr__(self): - # type: () -> str + def __repr__(self) -> str: s = "rooibos.Environment([{}])" - s = s.format(', '.join([repr(self[t]) for t in self])) - return s + return s.format(', '.join([repr(self[t]) for t in self])) - def __iter__(self): - # type: () -> Iterator[str] + def __iter__(self) -> Iterator[str]: """ - Returns an iterator over the names of the terms contained within - this environment. + Returns an iterator over the names of the terms within this + environment. """ return self.__bindings.keys().__iter__() - def __getitem__(self, term): - # type: (str) -> BoundTerm - """ - Fetches details of a particular term within this environment. + def __getitem__(self, term: str) -> BoundTerm: + """Fetches details of a particular term within this environment. Parameters: term: the name of the term. @@ -239,43 +195,34 @@ def __getitem__(self, term): return self.__bindings[term] -class Match(object): +class Match: """ Describes a single match of a given template in a source text as a mapping of template terms to snippets of source code. """ @staticmethod - def from_dict(d): - # type: (Dict[str, Any]) -> Match - """ - Constructs a match from a dictionary-based description. - """ - return Match(environment=Environment.from_dict(d['environment']), - location=LocationRange.from_string(d['location'])) + def from_dict(d: Dict[str, Any]) -> 'Match': + """Constructs a match from a dictionary-based description.""" + return Match(Environment.from_dict(d['environment']), + LocationRange.from_string(d['location'])) - def __init__(self, environment, location): - # type: (Environment, LocationRange) -> None - """ - Constructs a new match. + def __init__(self, env: Environment, loc: LocationRange) -> None: + """Constructs a new match. Parameters: - environment: an environment that describes the mapping from terms + env: an environment that describes the mapping from terms in the match template to snippets within the source text. - location: the location range over which the template was matched. + loc: the location range over which the template was matched. """ - self.__environment = environment - self.__location = location + self.__environment = env + self.__location = loc - def __repr__(self): - # type: () -> str + def __repr__(self) -> str: s = "rooibos.Match({}, {})" - s = s.format(str(self.__location), repr(self.__environment)) - return s + return s.format(str(self.__location), repr(self.__environment)) - def __getitem__(self, term): - # type: (str) -> BoundTerm - """ - Retrieves a bound term from this match. + def __getitem__(self, term: str) -> BoundTerm: + """Retrieves a bound term from this match. Parameters: term: the name of the term. @@ -289,8 +236,7 @@ def __getitem__(self, term): return self.__environment[term] @property - def environment(self): - # type: () -> Environment + def environment(self) -> Environment: """ The environment that defines the mapping from terms in the match template to snippets in the source code. @@ -298,26 +244,19 @@ def environment(self): return self.__environment @property - def location(self): - # type: () -> LocationRange - """ - The range of locations in the source text over which the template was - matched. - """ + def location(self) -> LocationRange: + """The range of locations in the text where the template matched.""" return self.__location -class Client(object): - """ - Provides an interface for communicating with a Rooibos server. - """ +class Client: + """Provides an interface for communicating with a Rooibos server.""" def __init__(self, - base_url, # type: str - timeout=30, # type: int - timeout_connection=30 # type: int - ): # type: (...) -> None - """ - Constructs a new client. + base_url: str, + timeout: int = 30, + timeout_connection: int = 30 + ) -> None: + """Constructs a new client. Parameters: base_url: the base URL of the Rooibos server. @@ -333,8 +272,6 @@ def __init__(self, """ self.__base_url = base_url self.__timeout = timeout - self.__logger = logging.getLogger('rooibos') # type: logging.Logger - self.__logger.setLevel(logging.INFO) # attempt to establish a connection url = self._url("status") @@ -355,26 +292,16 @@ def __init__(self, raise ConnectionFailure @property - def base_url(self): - # type: () -> str - """ - The base URL of the Rooibos server to which this client is attached. - """ + def base_url(self) -> str: + """The base URL of the server to which this client is attached.""" return self.__base_url - def _url(self, path): - # type: (str) -> str - """ - Computes the URL for a resource located at a given path on the server. - """ + def _url(self, path: str) -> str: + """Computes the URL for a resource on the server.""" return urljoin(self.__base_url, path) - def matches(self, - source, # type: str - template # type: str - ): # type: (...) -> Iterator[Match] - """ - Finds all matches of a given template within a source text. + def matches(self, source: str, template: str) -> Iterator[Match]: + """Finds all matches of a given template within a source text. Parameters: source: the source text to be searched. @@ -383,8 +310,8 @@ def matches(self, Returns: an iterator over all matches in the text. """ - logger = self.__logger - logger.info("finding matches of template [%s] in source: %s", template, source) + logger.info("finding matches of template [%s] in source: %s", + template, source) url = self._url("matches") payload = { 'source': source, @@ -407,14 +334,12 @@ def matches(self, yield match def substitute(self, - template, # type: str - args # type: Dict[str, str] - ): # type: (...) -> str - """ - Substitutes a given set of terms into a given template. - """ - logger = self.__logger - logger.info("substituting arguments (%s) into template (%s)", repr(args), template) + template: str, + args: Dict[str, str] + ) -> str: + """Substitutes a given set of terms into a given template.""" + logger.info("substituting arguments (%s) into template (%s)", + repr(args), template) url = self._url("substitute") payload = { 'template': template, @@ -428,18 +353,18 @@ def substitute(self, return response.text def rewrite(self, - source, # type: str - match, # type: str - rewrite, # type: str - args=None # type: Dict[str, str] - ): # type: (...) -> str + source: str, + match: str, + rewrite: str, + args: Optional[Dict[str, str]] = None + ) -> str: """ Rewrites all matches of a given template in a source text using a provided rewrite template and an optional set of arguments to that rewrite template. """ - logger = self.__logger - logger.info("performing rewriting of source (%s) using match template (%s), rewrite template (%s) and arguments (%s)", + logger.info("performing rewriting of source (%s) using match template " + "(%s), rewrite template (%s) and arguments (%s)", source, match, rewrite, repr(args)) if args is None: args = {} @@ -459,9 +384,9 @@ def rewrite(self, @contextmanager -def ephemeral_server(port=8888, # type: int - verbose=False # type: bool - ): # type: (...) -> Iterator[Client] +def ephemeral_server(port: int = 8888, + verbose: bool = False + ) -> Iterator[Client]: """ Launches an ephemeral server instance that will be immediately close when no longer in context. diff --git a/src/rooibos/exceptions.py b/src/rooibos/exceptions.py index 3a9878a..81dcb02 100644 --- a/src/rooibos/exceptions.py +++ b/src/rooibos/exceptions.py @@ -1,10 +1,9 @@ -__all__ = ['RooibosException', 'ConnectionFailure'] +# -*- coding: utf-8 -*- +__all__ = ('RooibosException', 'ConnectionFailure') class RooibosException(Exception): - """ - Base class used by all Rooibos exceptions. - """ + """Base class used by all Rooibos exceptions.""" class ConnectionFailure(RooibosException):