Skip to content
This repository was archived by the owner on Nov 3, 2023. It is now read-only.

Commit 0f09848

Browse files
committed
Merge branch 'master' into checked-codes
Conflicts: test_pep257.py
2 parents aa60d3a + 4fe27bd commit 0f09848

File tree

9 files changed

+354
-26
lines changed

9 files changed

+354
-26
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,6 @@ venv/
4646

4747
# generated rst
4848
docs/snippets/error_code_table.rst
49+
50+
# PyCharm files
51+
.idea

docs/conf.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,9 @@
5656
# built documents.
5757
#
5858
# The short X.Y version.
59-
version = '0.4.1'
59+
version = '0.5.0'
6060
# The full version, including alpha/beta/rc tags.
61-
release = '0.4.1'
61+
release = '0.5.0'
6262

6363
# The language for content autogenerated by Sphinx. Refer to documentation
6464
# for a list of supported languages.

docs/release_notes.rst

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,21 @@ Release Notes
33

44

55
Current Development Version
6-
--------------------------
6+
---------------------------
7+
8+
Bug Fixes
9+
10+
* Property setter and deleter methods are now treated as private and do not
11+
require docstrings separate from the main property method (#69, #107).
12+
13+
0.5.0 - March 14th, 2015
14+
------------------------
715

816
New Features
917

1018
* Added check D210: No whitespaces allowed surrounding docstring text (#95).
1119

12-
* Added real documentation rendering using Sphinx (#100).
20+
* Added real documentation rendering using Sphinx (#100, #101).
1321

1422
Bug Fixes
1523

@@ -19,6 +27,8 @@ Bug Fixes
1927
and the description. It now checks that there is *exactly* one blank line
2028
between them (#79).
2129

30+
* Fixed a bug where ``--match-dir`` was not properly respected (#108, #109).
31+
2232
0.4.1 - January 10th, 2015
2333
--------------------------
2434

pep257

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#! /usr/bin/env python
1+
#!/usr/bin/env python
22
from pep257 import main
33

44

pep257.py

Lines changed: 85 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,15 @@ def next(obj, default=nothing):
5252
return default
5353

5454

55-
__version__ = '0.5.0-alpha'
55+
# If possible (python >= 3.2) use tokenize.open to open files, so PEP 263
56+
# encoding markers are interpreted.
57+
try:
58+
tokenize_open = tk.open
59+
except AttributeError:
60+
tokenize_open = open
61+
62+
63+
__version__ = '0.5.0'
5664
__all__ = ('check', 'collect')
5765

5866
PROJECT_CONFIG = ('setup.cfg', 'tox.ini', '.pep257')
@@ -100,7 +108,8 @@ def __repr__(self):
100108

101109
class Definition(Value):
102110

103-
_fields = 'name _source start end docstring children parent'.split()
111+
_fields = ('name', '_source', 'start', 'end', 'decorators', 'docstring',
112+
'children', 'parent')
104113

105114
_human = property(lambda self: humanize(type(self).__name__))
106115
kind = property(lambda self: self._human.split()[-1])
@@ -122,7 +131,8 @@ def __str__(self):
122131

123132
class Module(Definition):
124133

125-
_fields = 'name _source start end docstring children parent _all'.split()
134+
_fields = ('name', '_source', 'start', 'end', 'decorators', 'docstring',
135+
'children', 'parent', '_all')
126136
is_public = True
127137
_nest = staticmethod(lambda s: {'def': Function, 'class': Class}[s])
128138
module = property(lambda self: self)
@@ -154,6 +164,11 @@ class Method(Function):
154164

155165
@property
156166
def is_public(self):
167+
# Check if we are a setter/deleter method, and mark as private if so.
168+
for decorator in self.decorators:
169+
# Given 'foo', match 'foo.bar' but not 'foobar' or 'sfoo'
170+
if re(r"^{0}\.".format(self.name)).match(decorator.name):
171+
return False
157172
name_is_public = not self.name.startswith('_') or is_magic(self.name)
158173
return self.parent.is_public and name_is_public
159174

@@ -169,6 +184,13 @@ class NestedClass(Class):
169184
is_public = False
170185

171186

187+
class Decorator(Value):
188+
189+
"""A decorator for function, method or class."""
190+
191+
_fields = 'name arguments'.split()
192+
193+
172194
class TokenKind(int):
173195
def __repr__(self):
174196
return "tk.{}".format(tk.tok_name[self])
@@ -225,6 +247,7 @@ def __call__(self, filelike, filename):
225247
self.stream = TokenStream(StringIO(src))
226248
self.filename = filename
227249
self.all = None
250+
self._accumulated_decorators = []
228251
return self.parse_module()
229252

230253
current = property(lambda self: self.stream.current)
@@ -260,13 +283,59 @@ def parse_docstring(self):
260283
return docstring
261284
return None
262285

286+
def parse_decorators(self):
287+
"""Called after first @ is found.
288+
289+
Parse decorators into self._accumulated_decorators.
290+
Continue to do so until encountering the 'def' or 'class' start token.
291+
"""
292+
name = []
293+
arguments = []
294+
at_arguments = False
295+
296+
while self.current is not None:
297+
if (self.current.kind == tk.NAME and
298+
self.current.value in ['def', 'class']):
299+
# Done with decorators - found function or class proper
300+
break
301+
elif self.current.kind == tk.OP and self.current.value == '@':
302+
# New decorator found. Store the decorator accumulated so far:
303+
self._accumulated_decorators.append(
304+
Decorator(''.join(name), ''.join(arguments)))
305+
# Now reset to begin accumulating the new decorator:
306+
name = []
307+
arguments = []
308+
at_arguments = False
309+
elif self.current.kind == tk.OP and self.current.value == '(':
310+
at_arguments = True
311+
elif self.current.kind == tk.OP and self.current.value == ')':
312+
# Ignore close parenthesis
313+
pass
314+
elif self.current.kind == tk.NEWLINE or self.current.kind == tk.NL:
315+
# Ignore newlines
316+
pass
317+
else:
318+
# Keep accumulating current decorator's name or argument.
319+
if not at_arguments:
320+
name.append(self.current.value)
321+
else:
322+
arguments.append(self.current.value)
323+
self.stream.move()
324+
325+
# Add decorator accumulated so far
326+
self._accumulated_decorators.append(
327+
Decorator(''.join(name), ''.join(arguments)))
328+
263329
def parse_definitions(self, class_, all=False):
264330
"""Parse multiple defintions and yield them."""
265331
while self.current is not None:
266332
log.debug("parsing defintion list, current token is %r (%s)",
267333
self.current.kind, self.current.value)
268334
if all and self.current.value == '__all__':
269335
self.parse_all()
336+
elif self.current.kind == tk.OP and self.current.value == '@':
337+
self.consume(tk.OP)
338+
self.parse_decorators()
270339
elif self.current.value in ['def', 'class']:
271340
yield self.parse_definition(class_._nest(self.current.value))
272341
elif self.current.kind == tk.INDENT:
@@ -330,7 +399,7 @@ def parse_module(self):
330399
assert self.current is None, self.current
331400
end = self.line
332401
module = Module(self.filename, self.source, start, end,
333-
docstring, children, None, self.all)
402+
[], docstring, children, None, self.all)
334403
for child in module.children:
335404
child.parent = module
336405
log.debug("finished parsing module.")
@@ -362,17 +431,20 @@ def parse_definition(self, class_):
362431
self.leapfrog(tk.INDENT)
363432
assert self.current.kind != tk.INDENT
364433
docstring = self.parse_docstring()
434+
decorators = self._accumulated_decorators
435+
self._accumulated_decorators = []
365436
log.debug("parsing nested defintions.")
366437
children = list(self.parse_definitions(class_))
367438
log.debug("finished parsing nested defintions for '%s'", name)
368439
end = self.line - 1
369440
else: # one-liner definition
370441
docstring = self.parse_docstring()
442+
decorators = [] # TODO
371443
children = []
372444
end = self.line
373445
self.leapfrog(tk.NEWLINE)
374446
definition = class_(name, self.source, start, end,
375-
docstring, children, None)
447+
decorators, docstring, children, None)
376448
for child in definition.children:
377449
child.parent = definition
378450
log.debug("finished parsing %s '%s'. Next token is %r (%s)",
@@ -641,7 +713,7 @@ def check(filenames, select=None, ignore=None):
641713
for filename in filenames:
642714
log.info('Checking file %s.', filename)
643715
try:
644-
with open(filename) as file:
716+
with tokenize_open(filename) as file:
645717
source = file.read()
646718
for error in PEP257Checker().check_source(source, filename):
647719
code = getattr(error, 'code', None)
@@ -999,11 +1071,12 @@ def check_triple_double_quotes(self, definition, docstring):
9991071
10001072
'''
10011073
if docstring and '"""' in eval(docstring) and docstring.startswith(
1002-
("'''", "r'''", "u'''")):
1074+
("'''", "r'''", "u'''", "ur'''")):
10031075
# Allow ''' quotes if docstring contains """, because otherwise """
10041076
# quotes could not be expressed inside docstring. Not in PEP 257.
10051077
return
1006-
if docstring and not docstring.startswith(('"""', 'r"""', 'u"""')):
1078+
if docstring and not docstring.startswith(
1079+
('"""', 'r"""', 'u"""', 'ur"""')):
10071080
quotes = "'''" if "'''" in docstring[:4] else "'"
10081081
return D300(quotes)
10091082

@@ -1017,7 +1090,8 @@ def check_backslashes(self, definition, docstring):
10171090
'''
10181091
# Just check that docstring is raw, check_triple_double_quotes
10191092
# ensures the correct quotes.
1020-
if docstring and '\\' in docstring and not docstring.startswith('r'):
1093+
if docstring and '\\' in docstring and not docstring.startswith(
1094+
('r', 'ur')):
10211095
return D301()
10221096

10231097
@check_for(Definition)
@@ -1030,7 +1104,8 @@ def check_unicode_docstring(self, definition, docstring):
10301104
# Just check that docstring is unicode, check_triple_double_quotes
10311105
# ensures the correct quotes.
10321106
if docstring and sys.version_info[0] <= 2:
1033-
if not is_ascii(docstring) and not docstring.startswith('u'):
1107+
if not is_ascii(docstring) and not docstring.startswith(
1108+
('u', 'ur')):
10341109
return D302()
10351110

10361111
@check_for(Definition)

0 commit comments

Comments
 (0)