From d0d1f4ad6f0190309be76ee486253995a547bf34 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 17 May 2015 20:15:42 -0400 Subject: All Python source is Unicode internally. Unfortunately, this meant hacking around a silly Python 2 restriction (can't compile a Unicode string containing an encoding declaration). --- coverage/python.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) (limited to 'coverage/python.py') diff --git a/coverage/python.py b/coverage/python.py index 19212a5..f335f16 100644 --- a/coverage/python.py +++ b/coverage/python.py @@ -8,12 +8,13 @@ import zipimport from coverage import env from coverage.backward import unicode_class from coverage.files import FileLocator -from coverage.misc import NoSource, join_regex +from coverage.misc import contract, NoSource, join_regex from coverage.parser import PythonParser from coverage.phystokens import source_token_lines, source_encoding from coverage.plugin import FileReporter +@contract(returns='str') def read_python_source(filename): """Read the Python source text from `filename`. @@ -30,8 +31,9 @@ def read_python_source(filename): return f.read() +@contract(returns='unicode') def get_python_source(filename): - """Return the source code, as a str.""" + """Return the source code, as unicode.""" base, ext = os.path.splitext(filename) if ext == ".py" and env.WINDOWS: exts = [".py", ".pyw"] @@ -49,12 +51,15 @@ def get_python_source(filename): source = get_zip_bytes(try_filename) if source is not None: if env.PY3: - source = source.decode(source_encoding(source)) + source = source.decode(source_encoding(source), "replace") break else: # Couldn't find source. raise NoSource("No source for code: '%s'." % filename) + if env.PY2: + source = source.decode(source_encoding(source), "replace") + # Python code should always end with a line with a newline. if source and source[-1] != '\n': source += '\n' @@ -62,6 +67,7 @@ def get_python_source(filename): return source +@contract(returns='bytes|None') def get_zip_bytes(filename): """Get data from `filename` if it is a zip file path. @@ -161,12 +167,10 @@ class PythonFileReporter(FileReporter): def exit_counts(self): return self.parser.exit_counts() + @contract(returns='unicode') def source(self): if self._source is None: self._source = get_python_source(self.filename) - if env.PY2: - encoding = source_encoding(self._source) - self._source = self._source.decode(encoding, "replace") assert isinstance(self._source, unicode_class) return self._source -- cgit v1.2.1 From e4e17518c99d4503c513410d164f28723968bdb7 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 17 May 2015 22:28:57 -0400 Subject: Files with incorrect encoding declarations are no longer ignored. #351 --- coverage/python.py | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) (limited to 'coverage/python.py') diff --git a/coverage/python.py b/coverage/python.py index f335f16..3e8e9f3 100644 --- a/coverage/python.py +++ b/coverage/python.py @@ -1,8 +1,6 @@ """Python source expertise for coverage.py""" import os.path -import sys -import tokenize import zipimport from coverage import env @@ -14,21 +12,15 @@ from coverage.phystokens import source_token_lines, source_encoding from coverage.plugin import FileReporter -@contract(returns='str') +@contract(returns='bytes') def read_python_source(filename): """Read the Python source text from `filename`. - Returns a str: unicode on Python 3, bytes on Python 2. + Returns bytes. """ - # Python 3.2 provides `tokenize.open`, the best way to open source files. - if sys.version_info >= (3, 2): - f = tokenize.open(filename) - else: - f = open(filename, "rU") - - with f: - return f.read() + with open(filename, "rb") as f: + return f.read().replace(b"\r\n", b"\n").replace(b"\r", b"\n") @contract(returns='unicode') @@ -50,15 +42,12 @@ def get_python_source(filename): # Maybe it's in a zip file? source = get_zip_bytes(try_filename) if source is not None: - if env.PY3: - source = source.decode(source_encoding(source), "replace") break else: # Couldn't find source. raise NoSource("No source for code: '%s'." % filename) - if env.PY2: - source = source.decode(source_encoding(source), "replace") + source = source.decode(source_encoding(source), "replace") # Python code should always end with a line with a newline. if source and source[-1] != '\n': -- cgit v1.2.1 From 6a7183dafc2b2ceba83527ecbdb485589ffc45c1 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 17 May 2015 22:53:02 -0400 Subject: Use more @contract, less assert --- coverage/python.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'coverage/python.py') diff --git a/coverage/python.py b/coverage/python.py index 3e8e9f3..faa26ff 100644 --- a/coverage/python.py +++ b/coverage/python.py @@ -77,7 +77,6 @@ def get_zip_bytes(filename): data = zi.get_data(parts[1]) except IOError: continue - assert isinstance(data, bytes) return data return None @@ -160,7 +159,6 @@ class PythonFileReporter(FileReporter): def source(self): if self._source is None: self._source = get_python_source(self.filename) - assert isinstance(self._source, unicode_class) return self._source def should_be_python(self): -- cgit v1.2.1 From 8ce8a504a49ee57d769405ae401bf4129b3d3341 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sat, 23 May 2015 07:37:35 -0400 Subject: Pylint weeding --- coverage/python.py | 1 - 1 file changed, 1 deletion(-) (limited to 'coverage/python.py') diff --git a/coverage/python.py b/coverage/python.py index faa26ff..69823da 100644 --- a/coverage/python.py +++ b/coverage/python.py @@ -4,7 +4,6 @@ import os.path import zipimport from coverage import env -from coverage.backward import unicode_class from coverage.files import FileLocator from coverage.misc import contract, NoSource, join_regex from coverage.parser import PythonParser -- cgit v1.2.1 From f230be76462efd15f48f216631f4d90db8d5685d Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sat, 13 Jun 2015 14:19:45 -0400 Subject: Change FileLocator from a class to module-level functions --- coverage/python.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'coverage/python.py') diff --git a/coverage/python.py b/coverage/python.py index 69823da..8dc163d 100644 --- a/coverage/python.py +++ b/coverage/python.py @@ -3,8 +3,7 @@ import os.path import zipimport -from coverage import env -from coverage.files import FileLocator +from coverage import env, files from coverage.misc import contract, NoSource, join_regex from coverage.parser import PythonParser from coverage.phystokens import source_token_lines, source_encoding @@ -85,7 +84,6 @@ class PythonFileReporter(FileReporter): def __init__(self, morf, coverage=None): self.coverage = coverage - file_locator = coverage.file_locator if coverage else FileLocator() if hasattr(morf, '__file__'): filename = morf.__file__ @@ -98,15 +96,13 @@ class PythonFileReporter(FileReporter): elif filename.endswith('$py.class'): # Jython filename = filename[:-9] + ".py" - super(PythonFileReporter, self).__init__( - file_locator.canonical_filename(filename) - ) + super(PythonFileReporter, self).__init__(files.canonical_filename(filename)) if hasattr(morf, '__name__'): name = morf.__name__ name = name.replace(".", os.sep) + ".py" else: - name = file_locator.relative_filename(filename) + name = files.relative_filename(filename) self.name = name self._source = None -- cgit v1.2.1 From 18f7ecbc364a2e95def74d6e7546e920cc38e238 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 14 Jun 2015 09:41:01 -0400 Subject: Debugging plugin wrappers --- coverage/python.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'coverage/python.py') diff --git a/coverage/python.py b/coverage/python.py index 8dc163d..b1667f6 100644 --- a/coverage/python.py +++ b/coverage/python.py @@ -103,13 +103,16 @@ class PythonFileReporter(FileReporter): name = name.replace(".", os.sep) + ".py" else: name = files.relative_filename(filename) - self.name = name + self.relname = name self._source = None self._parser = None self._statements = None self._excluded = None + def relative_filename(self): + return self.relname + @property def parser(self): if self._parser is None: -- cgit v1.2.1 From dc0d0c613de54cd5af74a1d3ac9d86235dc0aee9 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Fri, 24 Jul 2015 10:43:46 -0400 Subject: Add license mention to the top of all files. #313. --- coverage/python.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'coverage/python.py') diff --git a/coverage/python.py b/coverage/python.py index b1667f6..5c126c4 100644 --- a/coverage/python.py +++ b/coverage/python.py @@ -1,3 +1,6 @@ +# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt + """Python source expertise for coverage.py""" import os.path -- cgit v1.2.1 From fd2284632777a235ac663b7b9912246b30bc538b Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 26 Jul 2015 06:56:58 -0400 Subject: Get rid of napoleon style docstrings, they don't format nicely. --- coverage/python.py | 1 + 1 file changed, 1 insertion(+) (limited to 'coverage/python.py') diff --git a/coverage/python.py b/coverage/python.py index 5c126c4..94d20fd 100644 --- a/coverage/python.py +++ b/coverage/python.py @@ -118,6 +118,7 @@ class PythonFileReporter(FileReporter): @property def parser(self): + """Lazily create a :class:`PythonParser`.""" if self._parser is None: self._parser = PythonParser( filename=self.filename, -- cgit v1.2.1