Skip to content

Commit e209ff9

Browse files
xmo-odoouilianries
andauthored
Remove Python 2 compatibility shims (#34)
* Remove Python 2 compatibility shims Python 2 compatibility was removed in #31 so these should not be useful / necessary anymore. * Remove dead `sys` import The CLI mode uses `sys` internally but has its own import. * Remove `to_file_bytes` It does almost nothing at this point and is only used by one caller, so can be inlined. * Minor fixes for Python 3 compat Signed-off-by: Uilian Ries <uilianr@jfrog.com> * Fix nested next method Signed-off-by: Uilian Ries <uilianr@jfrog.com> * Validate applying 30MB patch file Signed-off-by: Uilian Ries <uilianr@jfrog.com> * Update CI to fix hanging Linux build Signed-off-by: Uilian Ries <uilianr@jfrog.com> * Use specific patch version for python due python-setup rule Signed-off-by: Uilian Ries <uilianr@jfrog.com> * Use ubuntu 22.04 to have python 3.7 available Signed-off-by: Uilian Ries <uilianr@jfrog.com> --------- Signed-off-by: Uilian Ries <uilianr@jfrog.com> Co-authored-by: Uilian Ries <uilianr@jfrog.com>
1 parent 0f9ea8f commit e209ff9

File tree

5 files changed

+73
-102
lines changed

5 files changed

+73
-102
lines changed

.github/workflows/workflow.yml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,15 @@ on:
2121
jobs:
2222
linux-validate:
2323
name: Validate on Linux - Python ${{ matrix.python }}
24-
runs-on: ubuntu-20.04
24+
runs-on: ubuntu-22.04
2525
strategy:
2626
matrix:
27-
python: [ '3.6', '3.8', '3.12' ]
27+
python: [ '3.7', '3.8', '3.12' ]
2828
steps:
29-
- uses: actions/checkout@v4
29+
- uses: actions/checkout@v5
3030

3131
- name: Setup python
32-
uses: actions/setup-python@v5
32+
uses: actions/setup-python@v6
3333
with:
3434
python-version: ${{ matrix.python }}
3535
architecture: x64
@@ -42,12 +42,12 @@ jobs:
4242
runs-on: windows-latest
4343
strategy:
4444
matrix:
45-
python: [ '3.6', '3.8', '3.12' ]
45+
python: [ '3.7', '3.8', '3.12' ]
4646
steps:
47-
- uses: actions/checkout@v4
47+
- uses: actions/checkout@v5
4848

4949
- name: Setup python
50-
uses: actions/setup-python@v5
50+
uses: actions/setup-python@v6
5151
with:
5252
python-version: ${{ matrix.python }}
5353
architecture: x64

example/example.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ def __init__(self):
88

99
def emit(self, record):
1010
logstr = self.format(record)
11-
print logstr
11+
print(logstr)
1212

1313
patchlog = logging.getLogger("patch")
1414
patchlog.handlers = []

patch_ng.py

Lines changed: 24 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -28,56 +28,23 @@
2828
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2929
SOFTWARE.
3030
"""
31-
from __future__ import print_function
32-
3331
__author__ = "Conan.io <info@conan.io>"
3432
__version__ = "1.19.0-dev"
3533
__license__ = "MIT"
3634
__url__ = "https://github.com/conan-io/python-patch"
3735

36+
import codecs
3837
import copy
38+
import io
3939
import logging
40-
import re
41-
import tempfile
42-
import codecs
43-
44-
# cStringIO doesn't support unicode in 2.5
45-
try:
46-
from StringIO import StringIO
47-
except ImportError:
48-
from io import BytesIO as StringIO # python 3
49-
try:
50-
import urllib2 as urllib_request
51-
except ImportError:
52-
import urllib.request as urllib_request
53-
54-
from os.path import exists, isfile, abspath
5540
import os
5641
import posixpath
42+
import re
5743
import shutil
58-
import sys
5944
import stat
60-
61-
62-
PY3K = sys.version_info >= (3, 0)
63-
64-
# PEP 3114
65-
if not PY3K:
66-
compat_next = lambda gen: gen.next()
67-
else:
68-
compat_next = lambda gen: gen.__next__()
69-
70-
def tostr(b):
71-
""" Python 3 bytes encoder. Used to print filename in
72-
diffstat output. Assumes that filenames are in utf-8.
73-
"""
74-
if not PY3K:
75-
return b
76-
77-
# [ ] figure out how to print non-utf-8 filenames without
78-
# information loss
79-
return b.decode('utf-8')
80-
45+
import tempfile
46+
import urllib.request
47+
from os.path import exists, isfile, abspath
8148

8249
#------------------------------------------------
8350
# Logging is controlled by logger named after the
@@ -90,22 +57,10 @@ def tostr(b):
9057
warning = logger.warning
9158
error = logger.error
9259

93-
class NullHandler(logging.Handler):
94-
""" Copied from Python 2.7 to avoid getting
95-
`No handlers could be found for logger "patch"`
96-
http://bugs.python.org/issue16539
97-
"""
98-
def handle(self, record):
99-
pass
100-
def emit(self, record):
101-
pass
102-
def createLock(self):
103-
self.lock = None
104-
10560
streamhandler = logging.StreamHandler()
10661

10762
# initialize logger itself
108-
logger.addHandler(NullHandler())
63+
logger.addHandler(logging.NullHandler())
10964

11065
debugmode = False
11166

@@ -194,19 +149,18 @@ def fromfile(filename):
194149
"""
195150
patchset = PatchSet()
196151
debug("reading %s" % filename)
197-
fp = open(filename, "rb")
198-
res = patchset.parse(fp)
199-
fp.close()
200-
if res == True:
201-
return patchset
152+
with open(filename, "rb") as fp:
153+
res = patchset.parse(fp)
154+
if res == True:
155+
return patchset
202156
return False
203157

204158

205159
def fromstring(s):
206160
""" Parse text string and return PatchSet()
207161
object (or False if parsing fails)
208162
"""
209-
ps = PatchSet( StringIO(s) )
163+
ps = PatchSet( io.BytesIO(s) )
210164
if ps.errors == 0:
211165
return ps
212166
return False
@@ -217,7 +171,7 @@ def fromurl(url):
217171
if an error occured. Note that this also
218172
can throw urlopen() exceptions.
219173
"""
220-
ps = PatchSet( urllib_request.urlopen(url) )
174+
ps = PatchSet( urllib.request.urlopen(url) )
221175
if ps.errors == 0:
222176
return ps
223177
return False
@@ -261,15 +215,6 @@ def decode_text(text):
261215
return text.decode("utf-8", "ignore") # Ignore not compatible characters
262216

263217

264-
def to_file_bytes(content):
265-
if PY3K:
266-
if not isinstance(content, bytes):
267-
content = bytes(content, "utf-8")
268-
elif isinstance(content, unicode):
269-
content = content.encode("utf-8")
270-
return content
271-
272-
273218
def load(path, binary=False):
274219
""" Loads a file content """
275220
with open(path, 'rb') as handle:
@@ -290,7 +235,9 @@ def save(path, content, only_if_modified=False):
290235
except Exception:
291236
pass
292237

293-
new_content = to_file_bytes(content)
238+
new_content = content
239+
if not isinstance(content, bytes):
240+
new_content = bytes(content, "utf-8")
294241

295242
if only_if_modified and os.path.exists(path):
296243
old_content = load(path, binary=True)
@@ -328,8 +275,7 @@ def __init__(self):
328275
self.type = None
329276

330277
def __iter__(self):
331-
for h in self.hunks:
332-
yield h
278+
return iter(self.hunks)
333279

334280

335281
class PatchSet(object):
@@ -359,8 +305,7 @@ def __len__(self):
359305
return len(self.items)
360306

361307
def __iter__(self):
362-
for i in self.items:
363-
yield i
308+
return iter(self.items)
364309

365310
def parse(self, stream):
366311
""" parse unified diff
@@ -394,7 +339,7 @@ def next(self):
394339
return False
395340

396341
try:
397-
self._lineno, self._line = compat_next(super(wrapumerate, self))
342+
self._lineno, self._line = super(wrapumerate, self).__next__()
398343
except StopIteration:
399344
self._exhausted = True
400345
self._line = False
@@ -902,7 +847,7 @@ def diffstat(self):
902847
#print(iratio, dratio, iwidth, dwidth, histwidth)
903848
hist = "+"*int(iwidth) + "-"*int(dwidth)
904849
# -- /calculating +- histogram --
905-
output += (format % (tostr(names[i]), str(insert[i] + delete[i]), hist))
850+
output += (format % (names[i].decode('utf-8'), str(insert[i] + delete[i]), hist))
906851

907852
output += (" %d files changed, %d insertions(+), %d deletions(-), %+d bytes"
908853
% (len(names), sum(insert), sum(delete), delta))
@@ -1270,15 +1215,11 @@ def get_line():
12701215

12711216

12721217
def write_hunks(self, srcname, tgtname, hunks):
1273-
src = open(srcname, "rb")
1274-
tgt = open(tgtname, "wb")
1275-
1276-
debug("processing target file %s" % tgtname)
1277-
1278-
tgt.writelines(self.patch_stream(src, hunks))
1218+
with open(srcname, "rb") as src, open(tgtname, "wb") as tgt:
1219+
debug("processing target file %s" % tgtname)
1220+
1221+
tgt.writelines(self.patch_stream(src, hunks))
12791222

1280-
tgt.close()
1281-
src.close()
12821223
# [ ] TODO: add test for permission copy
12831224
shutil.copymode(srcname, tgtname)
12841225
return True

tests/06nested/tests/app/EVENT_LOOP.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121

2222
class EVENT_LOOP(unittest.TestCase):
2323
def t_scheduled(self, interval, iterations, sleep_time=0):
24-
print 'Test interval=%s, iterations=%s, sleep=%s' % (interval,
25-
iterations, sleep_time)
24+
print('Test interval=%s, iterations=%s, sleep=%s' % (interval,
25+
iterations, sleep_time))
2626
warmup_iterations = iterations
2727

2828
self.last_t = 0.
@@ -76,6 +76,6 @@ def test_d01_50(self):
7676

7777
if __name__ == '__main__':
7878
if pyglet.version != '1.2dev':
79-
print 'Wrong version of pyglet imported; please check your PYTHONPATH'
79+
print('Wrong version of pyglet imported; please check your PYTHONPATH')
8080
else:
8181
unittest.main()

tests/run_tests.py

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,10 @@
3636
import re
3737
import shutil
3838
import unittest
39-
import copy
4039
import stat
4140
from os import listdir, chmod
4241
from os.path import abspath, dirname, exists, join, isdir, isfile
4342
from tempfile import mkdtemp
44-
try:
45-
getcwdu = os.getcwdu
46-
except AttributeError:
47-
getcwdu = os.getcwd # python 3, where getcwd always returns a unicode object
4843

4944
verbose = False
5045
if "-v" in sys.argv or "--verbose" in sys.argv:
@@ -150,7 +145,7 @@ def _run_test(self, testname):
150145
# 3.
151146
# test utility as a whole
152147
patch_tool = join(dirname(TESTS), "patch_ng.py")
153-
save_cwd = getcwdu()
148+
save_cwd = os.getcwd()
154149
os.chdir(tmpdir)
155150
extra = "-f" if "10fuzzy" in testname else ""
156151
if verbose:
@@ -204,7 +199,7 @@ def create_closure():
204199

205200
class TestCheckPatched(unittest.TestCase):
206201
def setUp(self):
207-
self.save_cwd = getcwdu()
202+
self.save_cwd = os.getcwd()
208203
os.chdir(TESTS)
209204

210205
def tearDown(self):
@@ -355,7 +350,7 @@ def test(self):
355350

356351
class TestPatchApply(unittest.TestCase):
357352
def setUp(self):
358-
self.save_cwd = getcwdu()
353+
self.save_cwd = os.getcwd()
359354
self.tmpdir = mkdtemp(prefix=self.__class__.__name__)
360355
os.chdir(self.tmpdir)
361356

@@ -457,6 +452,41 @@ def test_unlink_backup_windows(self):
457452
self.assertTrue(os.stat(some_file).st_mode, stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH)
458453

459454

455+
class TestHugePatchFile(unittest.TestCase):
456+
457+
def setUp(self):
458+
self.save_cwd = os.getcwd()
459+
self.tmpdir = mkdtemp(prefix=self.__class__.__name__)
460+
os.chdir(self.tmpdir)
461+
self.huge_patchfile = self._create_huge_patchfile(self.tmpdir)
462+
463+
def _create_huge_patchfile(self, tmpdir):
464+
""" Create a patch file with ~30MB of data
465+
"""
466+
hugefile = join(tmpdir, 'hugefile')
467+
with open(hugefile, 'wb') as f:
468+
for i in range(2500000):
469+
f.write(b'Line %d\n' % i)
470+
huge_patchfile = join(tmpdir, 'huge.patch')
471+
with open(huge_patchfile, 'wb') as f:
472+
f.write(b'--- a/hugefile\n')
473+
f.write(b'+++ b/hugefile\n')
474+
f.write(b'@@ -1,2500000 +1,2500000 @@\n')
475+
for i in range(2500000):
476+
f.write(b' Line %d\n' % i)
477+
return huge_patchfile
478+
479+
def tearDown(self):
480+
os.chdir(self.save_cwd)
481+
remove_tree_force(self.tmpdir)
482+
483+
def test_apply_huge_patch(self):
484+
""" Test that a huge patch file can be applied without issues
485+
"""
486+
pto = patch_ng.fromfile(self.huge_patchfile)
487+
self.assertTrue(pto.apply(root=self.tmpdir))
488+
489+
460490
class TestHelpers(unittest.TestCase):
461491
# unittest setting
462492
longMessage = True

0 commit comments

Comments
 (0)