Skip to content

Commit

Permalink
Autoformat Python source with ruff format
Browse files Browse the repository at this point in the history
Almost all of this repository was *already* using Black-style
formatting, despite there not actually being enforcement of style in CI.

Ruff's autoformatter is outrageously fast and (with small differences)
produces code formatted in the same style as Black.

Let's turn on the autoformatter!

This change was produced by:

1. Editing `pyproject.toml` and `tox.ini` to opt into `ruff format`
2. Changing `noqa F821` to `noqa: F821` (missing the colon means that
   *all* lint rules are ignored)
3. Running `ruff format .`
4. Moving `noqa` comments as necessary!
   ```
   ruff --fix .
   ruff --add-noqa .
   ```

Unfortunately, ruff doesn't allow telling the autoformatter to skip an
enforcement of quote style, so this diff is largely just changing quote
characters.
  • Loading branch information
DavidCain authored and logston committed May 15, 2024
1 parent 7a3a8a4 commit cd783e1
Show file tree
Hide file tree
Showing 17 changed files with 1,001 additions and 913 deletions.
20 changes: 10 additions & 10 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@
import os
import sys

sys.path.insert(0, os.path.abspath('../src/'))
sys.path.insert(0, os.path.abspath("../src/"))

master_doc = 'index'
master_doc = "index"

# -- Project information -----------------------------------------------------

project = 'scim2-filter-parser'
copyright = '2019, Paul Logston'
author = 'Paul Logston'
project = "scim2-filter-parser"
copyright = "2019, Paul Logston"
author = "Paul Logston"


# -- General configuration ---------------------------------------------------
Expand All @@ -30,26 +30,26 @@
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autodoc',
"sphinx.ext.autodoc",
]

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
templates_path = ["_templates"]

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]


# -- Options for HTML output -------------------------------------------------

# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'alabaster'
html_theme = "alabaster"

# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
html_static_path = ["_static"]
7 changes: 1 addition & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ select = [
]

ignore = [
# Line length can largely just be handled by an autoformatter.
# Consider making `ruff format` part of CI!
# Just let line length be handled by the autoformatter
"E501",
]

Expand Down Expand Up @@ -87,10 +86,6 @@ ruff = "^0.4.4"
toml = "^0.10.1"
tox = "^3.16.1"

[tool.black]
line-length = 100
skip-string-normalization = true

[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"
61 changes: 30 additions & 31 deletions speed_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,26 @@ def short():
filter = 'userName eq "test"'

attr_map = {
('name', 'familyname', None): 'name.familyname',
('emails', None, None): 'emails',
('emails', 'type', None): 'emails.type',
('emails', 'value', None): 'emails.value',
('username', None, None): 'username',
('title', None, None): 'title',
('usertype', None, None): 'usertype',
('schemas', None, None): 'schemas',
('username', None, 'urn:ietf:params:scim:schemas:core:2.0:user'): 'username',
('meta', 'lastmodified', None): 'meta.lastmodified',
('ims', 'type', None): 'ims.type',
('ims', 'value', None): 'ims.value',
("name", "familyname", None): "name.familyname",
("emails", None, None): "emails",
("emails", "type", None): "emails.type",
("emails", "value", None): "emails.value",
("username", None, None): "username",
("title", None, None): "title",
("usertype", None, None): "usertype",
("schemas", None, None): "schemas",
("username", None, "urn:ietf:params:scim:schemas:core:2.0:user"): "username",
("meta", "lastmodified", None): "meta.lastmodified",
("ims", "type", None): "ims.type",
("ims", "value", None): "ims.value",
}

joins = (
'LEFT JOIN emails ON emails.user_id = users.id',
'LEFT JOIN schemas ON schemas.user_id = users.id',
"LEFT JOIN emails ON emails.user_id = users.id",
"LEFT JOIN schemas ON schemas.user_id = users.id",
)

q = SQLiteQuery(filter, 'users', attr_map, joins)
q = SQLiteQuery(filter, "users", attr_map, joins)

q.sql

Expand All @@ -33,26 +33,25 @@ def long():
filter = 'emails[type eq "work" and value co "@example.com"] or ims[type eq "xmpp" and value co "@foo.com"]'

attr_map = {
('name', 'familyname', None): 'name.familyname',
('emails', None, None): 'emails',
('emails', 'type', None): 'emails.type',
('emails', 'value', None): 'emails.value',
('username', None, None): 'username',
('title', None, None): 'title',
('usertype', None, None): 'usertype',
('schemas', None, None): 'schemas',
('username', None, 'urn:ietf:params:scim:schemas:core:2.0:user'): 'username',
('meta', 'lastmodified', None): 'meta.lastmodified',
('ims', 'type', None): 'ims.type',
('ims', 'value', None): 'ims.value',
("name", "familyname", None): "name.familyname",
("emails", None, None): "emails",
("emails", "type", None): "emails.type",
("emails", "value", None): "emails.value",
("username", None, None): "username",
("title", None, None): "title",
("usertype", None, None): "usertype",
("schemas", None, None): "schemas",
("username", None, "urn:ietf:params:scim:schemas:core:2.0:user"): "username",
("meta", "lastmodified", None): "meta.lastmodified",
("ims", "type", None): "ims.type",
("ims", "value", None): "ims.value",
}

joins = (
'LEFT JOIN emails ON emails.user_id = users.id',
'LEFT JOIN schemas ON schemas.user_id = users.id',
"LEFT JOIN emails ON emails.user_id = users.id",
"LEFT JOIN schemas ON schemas.user_id = users.id",
)

q = SQLiteQuery(filter, 'users', attr_map, joins)
q = SQLiteQuery(filter, "users", attr_map, joins)

q.sql

79 changes: 41 additions & 38 deletions src/scim2_filter_parser/ast.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
'''
"""
This file defines classes for different kinds of nodes of an Abstract
Syntax Tree. In general, you will have a different AST node for
each kind of grammar rule.
This file has a small bit of metaprogramming to simplify specification
and to perform some validation steps.
'''
"""


class AST(object):
Expand All @@ -15,14 +15,14 @@ class AST(object):
def __init_subclass__(cls):
AST._nodes[cls.__name__] = cls

if not hasattr(cls, '__annotations__'):
if not hasattr(cls, "__annotations__"):
return

cls._fields = list(cls.__annotations__)

def __init__(self, *args, **kwargs):
if len(args) != len(self._fields):
raise TypeError(f'{str(self)}: Expected {len(self._fields)} arguments')
raise TypeError(f"{str(self)}: Expected {len(self._fields)} arguments")

for name, val in zip(self._fields, args):
setattr(self, name, val)
Expand All @@ -37,17 +37,18 @@ def __repr__(self):
val = getattr(self, name)

if isinstance(val, AST):
parts.append(f'{name}={type(val).__name__}')
parts.append(f"{name}={type(val).__name__}")

elif isinstance(val, list):
parts.append(f'{name}=[ len={len(val)} ]')
parts.append(f"{name}=[ len={len(val)} ]")

else:
parts.append(f'{name}={repr(val)}')
parts.append(f"{name}={repr(val)}")

argstr = ', '.join(parts)
argstr = ", ".join(parts)

return f"{type(self).__name__}({argstr})"

return f'{type(self).__name__}({argstr})'

# ----------------------------------------------------------------------
# Specific AST nodes.
Expand Down Expand Up @@ -75,41 +76,41 @@ def __repr__(self):


class Filter(AST):
expr : AST
negated : bool
namespace : AST
expr: AST
negated: bool
namespace: AST


class LogExpr(AST):
op : str
expr1 : Filter
expr2 : Filter
op: str
expr1: Filter
expr2: Filter


class SubAttr(AST):
value : str
value: str


class AttrPath(AST):
attr_name : str
sub_attr : (SubAttr, type(None))
uri : (str, type(None))
attr_name: str
sub_attr: (SubAttr, type(None))
uri: (str, type(None))

@property
def case_insensitive(self):
# userName is always case-insensitive
# https://datatracker.ietf.org/doc/html/rfc7643#section-4.1.1
return self.attr_name == 'userName'
return self.attr_name == "userName"


class CompValue(AST):
value : str
value: str


class AttrExpr(AST):
value : str
attr_path : AttrPath
comp_value : CompValue
value: str
attr_path: AttrPath
comp_value: CompValue

@property
def case_insensitive(self):
Expand All @@ -122,10 +123,11 @@ def case_insensitive(self):
# to make sure we're not writing duplicate definitions and to make
# sure methods match up with the names of actual AST nodes.


class VisitDict(dict):
def __setitem__(self, key, value):
if key in self:
raise AttributeError(f'Duplicate definition for {key}')
raise AttributeError(f"Duplicate definition for {key}")
super().__setitem__(key, value)


Expand All @@ -136,7 +138,7 @@ def __prepare__(cls, name, bases):


class NodeVisitor(metaclass=NodeVisitMeta):
'''
"""
Class for visiting nodes of the parse tree. This is modeled after
a similar class in the standard library ast.NodeVisitor. For each
node, the visit(node) method calls a method visit_NodeName(node)
Expand All @@ -156,47 +158,49 @@ class VisitOps(NodeVisitor):
tree = parse(txt)
VisitOps().visit(tree)
'''
"""

def visit(self, node):
'''
"""
Execute a method of the form visit_NodeName(node) where
NodeName is the name of the class of a particular node.
'''
"""
if isinstance(node, list):
for item in node:
self.visit(item)
elif isinstance(node, AST):
method = 'visit_' + node.__class__.__name__
method = "visit_" + node.__class__.__name__
visitor = getattr(self, method, self.generic_visit)
visitor(node)

def generic_visit(self, node):
'''
"""
Method executed if no applicable `visit_` method can be found.
This examines the node to see if it has `_fields`, is a list,
or can be further traversed.
'''
"""
for field in node._fields:
value = getattr(node, field, None)
self.visit(value)

@classmethod
def __init_subclass__(cls):
'''
"""
Sanity check. Make sure that visitor classes use the right names.
'''
"""
for key in vars(cls):
if key.startswith('visit_'):
if key.startswith("visit_"):
assert key[6:] in AST._nodes, f"{key} doesn't match any AST node" # noqa: SLF001


def flatten(top):
'''
"""
Flatten the entire parse tree into a list for the purposes of
debugging and testing. This returns a list of tuples of the
form (depth, node) where depth is an integer representing the
parse tree depth and node is the associated AST node.
'''
"""

class Flattener(NodeVisitor):
def __init__(self):
self.depth = 0
Expand All @@ -212,4 +216,3 @@ def generic_visit(self, node):
d.visit(top)

return d.nodes

Loading

0 comments on commit cd783e1

Please sign in to comment.