Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@theY4Kman
Created October 12, 2018 16:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save theY4Kman/25861e8acd564fe3a0e4a9f45b54a316 to your computer and use it in GitHub Desktop.
Save theY4Kman/25861e8acd564fe3a0e4a9f45b54a316 to your computer and use it in GitHub Desktop.
pytest plugin to allow matching class names on word boundaries
"""
In pytest.ini, we configure python_classes with wildcard patterns that are
checked with fnmatch. This can produce some unintentional effects; for instance,
the pattern For* also matches ForbidsAnonymousUsers, which we don't wish.
This plugin extends the semantics of fnmatch for python_classes, so a dash '-'
in the pattern matches a CamelWords boundary.
- For* will match "ForbidsAnonymousUsers" as well as "ForCurrentUser"
- For-* will match "ForCurrentUser", but not "ForbidsAnonymousUsers"
"""
import fnmatch
import re
import inflection
from _pytest.python import PyCollector, Class, Module, Instance
def underscore(word, lowercase=True):
"""
Make an underscored, optionally lowercase form from the expression in the string.
Example::
>>> underscore("DeviceType")
"device_type"
As a rule of thumb you can think of :func:`underscore` as the inverse of
:func:`camelize`, though there are cases where that does not hold::
>>> camelize(underscore("IOError"))
"IoError"
"""
word = re.sub(r"([A-Z]+)([A-Z][a-z])", r'\1_\2', word)
word = re.sub(r"([a-z\d])([A-Z])", r'\1_\2', word)
word = word.replace("-", "_")
if lowercase:
word = word.lower()
return word
def preprocess_camel_words(s: str) -> str:
"""Adds dashes between camel words, preserving underscores
>>> preprocess_camel_words('Forbids_AnonymousUsers')
'Forbids_Anonymous-Users'
>>> preprocess_camel_words('ForbidsAnonymousUsers')
'Forbids-Anonymous-Users'
>>> preprocess_camel_words('For8x5x4')
'For-8-x-5-x-4'
"""
# "Forbids_AnonymousUsers" -> "forbids_anonymous-users"
s = s.replace('_', ' ')
s = underscore(s, lowercase=False)
s = re.sub('(\d+)', r'_\1_', s)
s = s.replace('__', '_')
s = re.sub(' _|_ ', ' ', s)
s = s.strip('_')
s = inflection.dasherize(s)
s = s.replace(' ', '_')
return s
class CamelWordsSensitiveCollector(PyCollector):
def classnamefilter(self, name):
preprocessed_name = preprocess_camel_words(name)
for pattern in self.config.getini('python_classes'):
if name.startswith(pattern):
return True
# check that name looks like a glob-string before calling fnmatch
# because this is called for every name in each collected module,
# and fnmatch is somewhat expensive to call
elif '*' in pattern or '?' in pattern or '[' in pattern:
if fnmatch.fnmatch(preprocessed_name, pattern):
return True
return False
PyCollector.Module = type('Module', (CamelWordsSensitiveCollector, Module), {})
PyCollector.Class = type('Class', (CamelWordsSensitiveCollector, Class), {})
PyCollector.Instance = type('Instance', (CamelWordsSensitiveCollector, Instance), {})
def pytest_pycollect_makemodule(path, parent):
return PyCollector.Module(path, parent)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment