Skip to content

Commit

Permalink
Drop python 2 compatibility (#282)
Browse files Browse the repository at this point in the history
* Remove all the python2 things

* Require python >= 3.8 and declare official compatibility

* Test against all supported python versions

* Add py3.11, 3.12 to the officially supported versions

* Upgrade codebase constructs to python3.8+

* Add changelog entry

* Fix DeprecationWarning (maxsplit should be a kwarg)
  • Loading branch information
youtux committed Sep 16, 2024
1 parent 72c973b commit 1667cf8
Show file tree
Hide file tree
Showing 29 changed files with 66 additions and 128 deletions.
16 changes: 10 additions & 6 deletions .github/workflows/test-python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,15 @@ jobs:
matrix:
os:
- ubuntu-latest
python-version: [ '3.x', 'pypy-3.8', 'pypy-2.7' ]
# DISABLED: python-version: [ '3.x', '2.x' ]
# include:
# - os: macos-latest
# python-version: '3.x'
# - windows-latest
- macos-13
python-version:
- '3.12'
- '3.11'
- '3.10'
- '3.9'
- '3.8'
- 'pypy-3.8'

steps:
- uses: actions/checkout@v4
Expand All @@ -54,4 +58,4 @@ jobs:

- name: run acceptance tests
run: make acceptance
working-directory: python
working-directory: python
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ This document is formatted according to the principles of [Keep A CHANGELOG](htt
- [.NET] Enable warnings as errors
- [Ruby] Initial rubocop autofixes (Mainly to style) ([#266](https://github.com/cucumber/gherkin/pull/266))

### Removed
- [Python] Drop compatibility for python 2. Supported python versions are 3.8, 3.9, 3.10, 3.12

## [29.0.0] - 2024-08-12
### Added
- (i18n) Added Gujarati translation for "Rule" ([#249](https://github.com/cucumber/gherkin/pull/249))
Expand Down
13 changes: 4 additions & 9 deletions python/bin/gherkin
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
#!/usr/bin/env sh
# Use "make GHERKIN_PYTHON_VERSION=python2 ..." to use python2
if [ -z "$GHERKIN_PYTHON_VERSION" ];
then
if [ -x "$(command -v python)" ]
then
GHERKIN_PYTHON_VERSION=python
elif [ -x "$(command -v python3)" ]
if [ -x "$(command -v python3)" ]
then
GHERKIN_PYTHON_VERSION=python3
elif [ -x "$(command -v python2)" ]
elif [ -x "$(command -v python)" ]
then
GHERKIN_PYTHON_VERSION=python2
else
echo "Neiter python, python3 or python2 found on PATH, exiting"
GHERKIN_PYTHON_VERSION=python
echo "Neither python3 or python found on PATH, exiting"
exit 1
fi
fi
Expand Down
13 changes: 4 additions & 9 deletions python/bin/gherkin-generate-tokens
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
#!/usr/bin/env sh
# Use "make GHERKIN_PYTHON_VERSION=python2 ..." to use python2
if [ -z "$GHERKIN_PYTHON_VERSION" ];
then
if [ -x "$(command -v python)" ]
then
GHERKIN_PYTHON_VERSION=python
elif [ -x "$(command -v python3)" ]
if [ -x "$(command -v python3)" ]
then
GHERKIN_PYTHON_VERSION=python3
elif [ -x "$(command -v python2)" ]
elif [ -x "$(command -v python)" ]
then
GHERKIN_PYTHON_VERSION=python2
else
echo "Neiter python, python3 or python2 found on PATH, exiting"
GHERKIN_PYTHON_VERSION=python
echo "Neither python3 or python found on PATH, exiting"
exit 1
fi
fi
Expand Down
6 changes: 0 additions & 6 deletions python/bin/gherkin_generate_tokens.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
import codecs
import os
import sys
if sys.version_info < (3, 0):
import codecs
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
from gherkin.token_scanner import TokenScanner
from gherkin.token_formatter_builder import TokenFormatterBuilder
from gherkin.parser import Parser

files = sys.argv[1:]
if sys.version_info < (3, 0) and os.name != 'nt': # for Python2 unless on Windows native
UTF8Writer = codecs.getwriter('utf8')
sys.stdout = UTF8Writer(sys.stdout)
parser = Parser(TokenFormatterBuilder())
for file in files:
scanner = TokenScanner(file)
Expand Down
9 changes: 3 additions & 6 deletions python/gherkin-python.razor
Original file line number Diff line number Diff line change
Expand Up @@ -41,24 +41,21 @@ RULE_TYPE = [
]


class ParserContext(object):
class ParserContext:
def __init__(self, token_scanner, token_matcher, token_queue, errors):
self.token_scanner = token_scanner
self.token_matcher = token_matcher
self.token_queue = token_queue
self.errors = errors


class @(Model.ParserClassName)(object):
class @(Model.ParserClassName):
def __init__(self, ast_builder=None):
self.ast_builder = ast_builder if ast_builder is not None else AstBuilder()
self.stop_at_first_error = False

def parse(self, token_scanner_or_str, token_matcher=None):
if sys.version_info < (3, 0):
token_scanner = TokenScanner(token_scanner_or_str) if isinstance(token_scanner_or_str, basestring) else token_scanner_or_str
else:
token_scanner = TokenScanner(token_scanner_or_str) if isinstance(token_scanner_or_str, str) else token_scanner_or_str
token_scanner = TokenScanner(token_scanner_or_str) if isinstance(token_scanner_or_str, str) else token_scanner_or_str
self.ast_builder.reset()
if token_matcher is None:
token_matcher = TokenMatcher()
Expand Down
8 changes: 0 additions & 8 deletions python/gherkin/__main__.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
import os
from optparse import OptionParser
import sys
if sys.version_info < (3, 0):
string_type = basestring
if os.name != 'nt':
import codecs
UTF8Writer = codecs.getwriter('utf8')
sys.stdout = UTF8Writer(sys.stdout)
else:
string_type = str

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
import json
Expand Down
2 changes: 1 addition & 1 deletion python/gherkin/ast_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from .errors import AstBuilderException
from .stream.id_generator import IdGenerator

class AstBuilder(object):
class AstBuilder:
def __init__(self, id_generator=None):
self.id_generator = id_generator
if self.id_generator is None:
Expand Down
2 changes: 1 addition & 1 deletion python/gherkin/ast_node.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from collections import defaultdict


class AstNode(object):
class AstNode:

def __init__(self, rule_type):
self.rule_type = rule_type
Expand Down
6 changes: 0 additions & 6 deletions python/gherkin/count_symbols.py

This file was deleted.

6 changes: 0 additions & 6 deletions python/gherkin/count_symbols_py2.py

This file was deleted.

2 changes: 0 additions & 2 deletions python/gherkin/count_symbols_py3_plus.py

This file was deleted.

4 changes: 2 additions & 2 deletions python/gherkin/dialect.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
os.path.dirname(__file__),
'gherkin-languages.json')

with io.open(DIALECT_FILE_PATH, 'r', encoding='utf-8') as file:
with open(DIALECT_FILE_PATH, encoding='utf-8') as file:
DIALECTS = json.load(file)


class Dialect(object):
class Dialect:

@classmethod
def for_name(cls, name):
Expand Down
10 changes: 5 additions & 5 deletions python/gherkin/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ class ParserError(Exception):
class ParserException(ParserError):
def __init__(self, message, location):
self.location = location
super(ParserException, self).__init__('(' + str(location['line']) + ':' +
super().__init__('(' + str(location['line']) + ':' +
str(location['column'] if 'column' in
location else 0) + '): ' + message)


class NoSuchLanguageException(ParserException):
def __init__(self, language, location):
super(NoSuchLanguageException, self).__init__('Language not supported: ' + language,
super().__init__('Language not supported: ' + language,
location)


Expand All @@ -23,7 +23,7 @@ class AstBuilderException(ParserException):
class UnexpectedEOFException(ParserException):
def __init__(self, received_token, expected_token_types, state_comment):
message = 'unexpected end of file, expected: ' + ', '.join(expected_token_types)
super(UnexpectedEOFException, self).__init__(message, received_token.location)
super().__init__(message, received_token.location)


class UnexpectedTokenException(ParserException):
Expand All @@ -34,12 +34,12 @@ def __init__(self, received_token, expected_token_types, state_comment):
location = (received_token.location if column else
{'line': received_token.location['line'],
'column': received_token.line.indent + 1})
super(UnexpectedTokenException, self).__init__(message, location)
super().__init__(message, location)


class CompositeParserException(ParserError):
def __init__(self, errors):
self.errors = errors
super(CompositeParserException, self).__init__("Parser errors:\n" +
super().__init__("Parser errors:\n" +
'\n'.join([error.args[0] for error in
errors]))
4 changes: 2 additions & 2 deletions python/gherkin/gherkin_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from .errors import ParserException


class GherkinLine(object):
class GherkinLine:
def __init__(self, line_text, line_number):
self._line_text = line_text
self._line_number = line_number
Expand Down Expand Up @@ -76,7 +76,7 @@ def split_table_cells(self, row):
@property
def tags(self):
column = self.indent + 1
uncommented_line = re.split(r"\s#", self._trimmed_line_text.strip(), 2)[0]
uncommented_line = re.split(r"\s#", self._trimmed_line_text.strip(), maxsplit=2)[0]
items = uncommented_line.strip().split('@')
tags = []
for item in items[1:]:
Expand Down
3 changes: 1 addition & 2 deletions python/gherkin/inout.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
from __future__ import print_function
import json
from .parser import Parser
from .token_scanner import TokenScanner
from .pickles.compiler import compile
from .errors import ParserException, CompositeParserException

class Inout(object):
class Inout:
def __init__(self, print_source, print_ast, print_pickles):
self.print_source = print_source
self.print_ast = print_ast
Expand Down
9 changes: 3 additions & 6 deletions python/gherkin/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,24 +43,21 @@
]


class ParserContext(object):
class ParserContext:
def __init__(self, token_scanner, token_matcher, token_queue, errors):
self.token_scanner = token_scanner
self.token_matcher = token_matcher
self.token_queue = token_queue
self.errors = errors


class Parser(object):
class Parser:
def __init__(self, ast_builder=None):
self.ast_builder = ast_builder if ast_builder is not None else AstBuilder()
self.stop_at_first_error = False

def parse(self, token_scanner_or_str, token_matcher=None):
if sys.version_info < (3, 0):
token_scanner = TokenScanner(token_scanner_or_str) if isinstance(token_scanner_or_str, basestring) else token_scanner_or_str
else:
token_scanner = TokenScanner(token_scanner_or_str) if isinstance(token_scanner_or_str, str) else token_scanner_or_str
token_scanner = TokenScanner(token_scanner_or_str) if isinstance(token_scanner_or_str, str) else token_scanner_or_str
self.ast_builder.reset()
if token_matcher is None:
token_matcher = TokenMatcher()
Expand Down
6 changes: 2 additions & 4 deletions python/gherkin/pickles/compiler.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import re

from ..count_symbols import count_symbols
from ..stream.id_generator import IdGenerator


class Compiler(object):
class Compiler:
def __init__(self, id_generator=None):
self.id_generator = id_generator
if self.id_generator is None:
Expand Down Expand Up @@ -158,7 +156,7 @@ def _interpolate(self, name, variable_cells, value_cells):
# For the case of trailing backslash, re-escaping backslashes are needed
reescaped_value = re.sub(r'\\', r'\\\\', value_cell['value'])
name = re.sub(
u'<{0[value]}>'.format(variable_cell),
'<{0[value]}>'.format(variable_cell),
reescaped_value,
name
)
Expand Down
6 changes: 2 additions & 4 deletions python/gherkin/stream/gherkin_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,6 @@ def enum(self, source_event):
'pickle': pickle
}
except CompositeParserException as e:
for event in create_errors(e.errors, uri):
yield event
yield from create_errors(e.errors, uri)
except ParserError as e:
for event in create_errors([e], uri):
yield event
yield from create_errors([e], uri)
2 changes: 1 addition & 1 deletion python/gherkin/stream/id_generator.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
class IdGenerator(object):
class IdGenerator:
def __init__(self):
self._id_counter = 0

Expand Down
4 changes: 1 addition & 3 deletions python/gherkin/stream/source_events.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import io

def source_event(path):
event = {
'source': {
'uri': path,
'data': io.open(path, 'r', encoding='utf8', newline='').read(),
'data': open(path, encoding='utf8', newline='').read(),
'mediaType': 'text/x.cucumber.gherkin+plain'
}
}
Expand Down
2 changes: 1 addition & 1 deletion python/gherkin/token.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
class Token(object):
class Token:
def __init__(self, gherkin_line, location):
self.line = gherkin_line
self.location = location
Expand Down
15 changes: 2 additions & 13 deletions python/gherkin/token_matcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,8 @@
from .dialect import Dialect
from .errors import NoSuchLanguageException

# Source: https://stackoverflow.com/a/8348914
try:
import textwrap
textwrap.indent
except AttributeError: # undefined function (wasn't added until Python 3.3)
def indent(text, amount, ch=' '):
padding = amount * ch
return ''.join(padding+line for line in text.splitlines(True))
else:
def indent(text, amount, ch=' '):
return textwrap.indent(text, amount * ch)

class TokenMatcher(object):

class TokenMatcher:
LANGUAGE_RE = re.compile(r"^\s*#\s*language\s*:\s*([a-zA-Z\-_]+)\s*$")

def __init__(self, dialect_name='en'):
Expand Down
6 changes: 3 additions & 3 deletions python/gherkin/token_matcher_markdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
class GherkinInMarkdownTokenMatcher(TokenMatcher):

def __init__(self, dialect_name='en'):
super(GherkinInMarkdownTokenMatcher, self).__init__(dialect_name)
super().__init__(dialect_name)

def reset(self):
super(GherkinInMarkdownTokenMatcher, self).reset()
super().reset()
self.matched_feature_line=False

def match_FeatureLine(self, token):
Expand Down Expand Up @@ -142,7 +142,7 @@ def _default_docstring_content_type():
def _match_title_line(self, prefix, keywords, keywordSuffix, token, token_type):

keywords_or_list="|".join(map(lambda x: re.escape(x), keywords))
match = re.search(u'{}({}){}(.*)'.format(prefix, keywords_or_list, keywordSuffix), token.line.get_line_text())
match = re.search(f'{prefix}({keywords_or_list}){keywordSuffix}(.*)', token.line.get_line_text())
indent = token.line.indent

if(match):
Expand Down
Loading

0 comments on commit 1667cf8

Please sign in to comment.