Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .github/workflows/mypy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Static type checking

name: Static type checking

on:
push:
branches: ["master"]
pull_request:
branches: ["master"]

jobs:
test:
name: Static type checking
runs-on: ubuntu-latest
container: python:3.13-alpine3.21
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if we want to use alpine3.21 here, renovate bots won't bump 3.21. Maybe just alpine or use setup-python?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That part was copied verbatim from the other workflow (runtime.yml).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm happy to change it of course, just wanted to point out that sticking to a specific alpine version was not my own decision.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, it's fine then. We can tackle it separately.


steps:
- name: Install Alpine Dependencies
run: apk add git
- name: Display Python version
run: python -c "import sys; print(sys.version)"
- name: Display Git version
run: git --version

- uses: actions/checkout@v4
with:
submodules: recursive

- name: Install Dependencies
run: pip install -e ".[dev]"
- name: Run mypy
run: mypy --strict editorconfig
29 changes: 0 additions & 29 deletions .travis.yml

This file was deleted.

4 changes: 3 additions & 1 deletion editorconfig/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""EditorConfig Python Core"""

from collections import OrderedDict

from editorconfig.versiontools import join_version
from editorconfig.version import VERSION

Expand All @@ -8,7 +10,7 @@
__version__ = join_version(VERSION)


def get_properties(filename):
def get_properties(filename: str) -> OrderedDict[str, str]:
"""Locate and parse EditorConfig files for the given filename"""
handler = EditorConfigHandler(filename)
return handler.get_configurations()
Expand Down
17 changes: 9 additions & 8 deletions editorconfig/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@
import getopt
import sys

from editorconfig import VERSION, __version__
from editorconfig.compat import force_unicode
from editorconfig import __version__
from editorconfig.exceptions import ParsingError, PathError, VersionError
from editorconfig.handler import EditorConfigHandler
from editorconfig.version import VERSION
from editorconfig.versiontools import split_version


def version():
def version() -> None:
print("EditorConfig Python Core Version %s" % __version__)


def usage(command, error=False):
def usage(command: str, error: bool = False) -> None:
if error:
out = sys.stderr
else:
Expand All @@ -32,10 +32,10 @@ def usage(command, error=False):
out.write("-v OR --version Display version information.\n")


def main():
def main() -> None:
command_name = sys.argv[0]
try:
opts, args = getopt.getopt(list(map(force_unicode, sys.argv[1:])),
opts, args = getopt.getopt(sys.argv[1:],
"vhb:f:", ["version", "help"])
except getopt.GetoptError as e:
print(str(e))
Expand All @@ -55,9 +55,10 @@ def main():
if option == '-f':
conf_filename = arg
if option == '-b':
version_tuple = split_version(arg)
if version_tuple is None:
arg_tuple = split_version(arg)
if arg_tuple is None:
sys.exit("Invalid version number: %s" % arg)
version_tuple = arg_tuple

if len(args) < 1:
usage(command_name, error=True)
Expand Down
24 changes: 0 additions & 24 deletions editorconfig/compat.py

This file was deleted.

5 changes: 1 addition & 4 deletions editorconfig/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,7 @@ class EditorConfigError(Exception):
"""Parent class of all exceptions raised by EditorConfig"""


try:
from ConfigParser import ParsingError as _ParsingError
except:
from configparser import ParsingError as _ParsingError
from configparser import ParsingError as _ParsingError


class ParsingError(_ParsingError, EditorConfigError):
Expand Down
11 changes: 6 additions & 5 deletions editorconfig/fnmatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import os
import re
from re import Pattern


__all__ = ["fnmatch", "fnmatchcase", "translate"]
Expand Down Expand Up @@ -62,7 +63,7 @@
)


def fnmatch(name, pat):
def fnmatch(name: str, pat: str) -> bool:
"""Test whether FILENAME matches PATTERN.

Patterns are Unix shell style:
Expand All @@ -84,15 +85,15 @@ def fnmatch(name, pat):
return fnmatchcase(name, pat)


def cached_translate(pat):
def cached_translate(pat: str) -> tuple[Pattern[str], list[tuple[int, int]]]:
if not pat in _cache:
res, num_groups = translate(pat)
regex = re.compile(res)
_cache[pat] = regex, num_groups
return _cache[pat]


def fnmatchcase(name, pat):
def fnmatchcase(name: str, pat: str) -> bool:
"""Test whether FILENAME matches PATTERN, including case.

This is a version of fnmatch() which doesn't case-normalize
Expand All @@ -111,7 +112,7 @@ def fnmatchcase(name, pat):
return pattern_matched


def translate(pat, nested=False):
def translate(pat: str, nested: bool = False) -> tuple[str, list[tuple[int, int]]]:
"""Translate a shell PATTERN to a regular expression.

There is no way to quote meta-characters.
Expand Down Expand Up @@ -180,7 +181,7 @@ def translate(pat, nested=False):
if not has_comma and pos < length:
num_range = NUMERIC_RANGE.match(pat[index:pos])
if num_range:
numeric_groups.append(map(int, num_range.groups()))
numeric_groups.append((int(num_range.group(1)), int(num_range.group(2))))
result += r"([+-]?\d+)"
else:
inner_result, inner_groups = translate(pat[index:pos],
Expand Down
27 changes: 14 additions & 13 deletions editorconfig/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,18 @@
"""

import os
from collections import OrderedDict

from editorconfig import VERSION
from editorconfig.exceptions import PathError, VersionError
from editorconfig.ini import EditorConfigParser
from editorconfig.version import VERSION
from editorconfig.versiontools import VersionTuple


__all__ = ['EditorConfigHandler']


def get_filenames(path, filename):
def get_filenames(path: str, filename: str) -> list[str]:
"""Yield full filepath for filename in each directory in and above path"""
path_list = []
while True:
Expand All @@ -40,15 +42,15 @@ class EditorConfigHandler(object):

"""

def __init__(self, filepath, conf_filename='.editorconfig',
version=VERSION):
def __init__(self, filepath: str, conf_filename: str = '.editorconfig',
version: VersionTuple = VERSION):
"""Create EditorConfigHandler for matching given filepath"""
self.filepath = filepath
self.conf_filename = conf_filename
self.version = version
self.options = None
self.filepath: str = filepath
self.conf_filename: str = conf_filename
self.version: VersionTuple = version
self.options: OrderedDict[str, str] = OrderedDict()

def get_configurations(self):
def get_configurations(self) -> OrderedDict[str, str]:

"""
Find EditorConfig files and return all options matching filepath
Expand All @@ -73,8 +75,7 @@ def get_configurations(self):
# Merge new EditorConfig file's options into current options
old_options = self.options
self.options = parser.options
if old_options:
self.options.update(old_options)
self.options.update(old_options)

# Stop parsing if parsed file has a ``root = true`` option
if parser.root_file:
Expand All @@ -83,7 +84,7 @@ def get_configurations(self):
self.preprocess_values()
return self.options

def check_assertions(self):
def check_assertions(self) -> None:

"""Raise error if filepath or version have invalid values"""

Expand All @@ -96,7 +97,7 @@ def check_assertions(self):
raise VersionError(
"Required version is greater than the current version.")

def preprocess_values(self):
def preprocess_values(self) -> None:

"""Preprocess option values for consumption by plugins"""

Expand Down
21 changes: 10 additions & 11 deletions editorconfig/ini.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,11 @@

import posixpath
import re
from codecs import open
from codecs import open, StreamReaderWriter
from collections import OrderedDict
from os import sep
from os.path import dirname, normpath

from editorconfig.compat import u
from editorconfig.exceptions import ParsingError
from editorconfig.fnmatch import fnmatch

Expand Down Expand Up @@ -76,12 +75,12 @@ class EditorConfigParser(object):
""", re.VERBOSE
)

def __init__(self, filename):
self.filename = filename
self.options = OrderedDict()
self.root_file = False
def __init__(self, filename: str):
self.filename: str = filename
self.options: OrderedDict[str, str] = OrderedDict()
self.root_file: bool = False

def matches_filename(self, config_filename, glob):
def matches_filename(self, config_filename: str, glob: str) -> bool:
"""Return True if section glob matches filename"""
config_dirname = normpath(dirname(config_filename)).replace(sep, '/')
glob = glob.replace("\\#", "#")
Expand All @@ -94,7 +93,7 @@ def matches_filename(self, config_filename, glob):
glob = posixpath.join('**/', glob)
return fnmatch(self.filename, glob)

def read(self, filename):
def read(self, filename: str) -> None:
"""Read and parse single EditorConfig file"""
try:
fp = open(filename, encoding='utf-8')
Expand All @@ -103,7 +102,7 @@ def read(self, filename):
self._read(fp, filename)
fp.close()

def _read(self, fp, fpname):
def _read(self, fp: StreamReaderWriter, fpname: str) -> None:
"""Parse a sectioned setup file.

The sections in setup file contains a title line at the top,
Expand All @@ -122,7 +121,7 @@ def _read(self, fp, fpname):
line = fp.readline()
if not line:
break
if lineno == 0 and line.startswith(u('\ufeff')):
if lineno == 0 and line.startswith('\ufeff'):
line = line[1:] # Strip UTF-8 BOM
lineno = lineno + 1
# comment or blank line?
Expand Down Expand Up @@ -170,5 +169,5 @@ def _read(self, fp, fpname):
if e:
raise e

def optionxform(self, optionstr):
def optionxform(self, optionstr: str) -> str:
return optionstr.lower()
Empty file added editorconfig/py.typed
Empty file.
9 changes: 5 additions & 4 deletions editorconfig/versiontools.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,25 @@
"""

import re
from typing import Optional


__all__ = ['join_version', 'split_version']


_version_re = re.compile(r'^(\d+)\.(\d+)\.(\d+)(\..*)?$', re.VERBOSE)

VersionTuple = tuple[int, int, int, str]

def join_version(version_tuple):
def join_version(version_tuple: VersionTuple) -> str:
"""Return a string representation of version from given VERSION tuple"""
version = "%s.%s.%s" % version_tuple[:3]
if version_tuple[3] != "final":
version += "-%s" % version_tuple[3]
return version


def split_version(version):
def split_version(version: str) -> Optional[VersionTuple]:
"""Return VERSION tuple for given string representation of version"""
match = _version_re.search(version)
if not match:
Expand All @@ -31,5 +33,4 @@ def split_version(version):
split_version = list(match.groups())
if split_version[3] is None:
split_version[3] = "final"
split_version = list(map(int, split_version[:3])) + split_version[3:]
return tuple(split_version)
return (int(split_version[0]), int(split_version[1]), int(split_version[2]), split_version[3])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the old code looks fine.

Suggested change
return (int(split_version[0]), int(split_version[1]), int(split_version[2]), split_version[3])
return tuple(list(map(int, split_version[:3])) + split_version[3:])

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mypy doesn't understand the old code, that's why I changed it. (Also, I think it's much more readable after the change, but that's just my opinion.)

Loading