Skip to content

Commit 7278f45

Browse files
committed
add support for python 3.12
1 parent ae3ab8e commit 7278f45

File tree

9 files changed

+110
-84
lines changed

9 files changed

+110
-84
lines changed

.github/workflows/tests.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ jobs:
3434
- python-version: '3.10'
3535
os: ubuntu-latest
3636
tox-env: py
37+
- python-version: '3.11'
38+
os: ubuntu-latest
39+
tox-env: py
40+
- python-version: '3.12'
41+
os: ubuntu-latest
42+
tox-env: py
3743

3844
steps:
3945
- uses: actions/checkout@v2
@@ -74,6 +80,10 @@ jobs:
7480
tox-env: py
7581
- python-version: '3.10'
7682
tox-env: py
83+
- python-version: '3.11'
84+
tox-env: py
85+
- python-version: '3.12'
86+
tox-env: py
7787

7888
steps:
7989
- uses: actions/checkout@v2
@@ -108,6 +118,10 @@ jobs:
108118
tox-env: py
109119
- python-version: '3.10'
110120
tox-env: py
121+
- python-version: '3.11'
122+
tox-env: py
123+
- python-version: '3.12'
124+
tox-env: py
111125

112126
steps:
113127
- uses: actions/checkout@v2

setup.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
install_requires=[
3333
'click',
3434
'docker',
35+
'importlib-metadata; python_version < "3.8"',
36+
'packaging',
3537
'pip',
3638
'PyYAML',
3739
'retrying',
@@ -52,6 +54,8 @@
5254
'Programming Language :: Python :: 3.8',
5355
'Programming Language :: Python :: 3.9',
5456
'Programming Language :: Python :: 3.10',
57+
'Programming Language :: Python :: 3.11',
58+
'Programming Language :: Python :: 3.12',
5559
'Operating System :: OS Independent',
5660
'Environment :: Console',
5761
'Topic :: Internet :: WWW/HTTP',

shub/deploy_egg.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import absolute_import
22
import os
33
import tempfile
4+
from shutil import which
45

56
import click
67

@@ -9,7 +10,7 @@
910
from shub.exceptions import (BadParameterException, NotFoundException,
1011
SubcommandException)
1112
from shub.utils import (decompress_egg_files, download_from_pypi,
12-
find_executable, run_cmd)
13+
run_cmd)
1314

1415

1516
HELP = """
@@ -86,7 +87,7 @@ def _checkout(repo, git_branch=None, target_dir='egg-tmp-clone'):
8687
]
8788
missing_exes = []
8889
for cmd in vcs_commands:
89-
exe = find_executable(cmd[0])
90+
exe = which(cmd[0])
9091
if not exe:
9192
missing_exes.append(cmd[0])
9293
continue
@@ -109,7 +110,7 @@ def _checkout(repo, git_branch=None, target_dir='egg-tmp-clone'):
109110

110111
if git_branch:
111112
try:
112-
run_cmd([find_executable('git'), 'checkout', git_branch])
113+
run_cmd([which('git'), 'checkout', git_branch])
113114
except SubcommandException:
114115
raise BadParameterException("Branch %s is not valid" % git_branch)
115116
click.echo("%s branch was checked out" % git_branch)

shub/image/run/wrapper.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
import logging
3030
import datetime
3131
from multiprocessing import Process
32-
from distutils.spawn import find_executable
32+
from shutil import which
3333

3434

3535
def _consume_from_fifo(fifo_path):
@@ -68,7 +68,7 @@ def main():
6868
# non-daemon to allow it to finish reading from pipe before exit.
6969
Process(target=_consume_from_fifo, args=[fifo_path]).start()
7070
# replace current process with original start-crawl
71-
os.execv(find_executable('start-crawl'), sys.argv)
71+
os.execv(which('start-crawl'), sys.argv)
7272

7373

7474
if __name__ == '__main__':

shub/image/utils.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import yaml
1010
from tqdm import tqdm
1111
from six import binary_type
12-
import pkg_resources
1312

1413
from shub import config as shub_config
1514
from shub import utils as shub_utils
@@ -18,6 +17,11 @@
1817
ShubDeprecationWarning, print_warning, BadParameterException,
1918
)
2019

20+
if sys.version_info < (3, 8):
21+
import importlib_metadata as metadata
22+
else:
23+
from importlib import metadata
24+
2125

2226
DEFAULT_DOCKER_API_VERSION = '1.21'
2327
STATUS_FILE_LOCATION = '.releases'
@@ -83,8 +87,8 @@ def get_docker_client(validate=True):
8387
import docker
8488
except ImportError:
8589
raise ImportError(DOCKER_PY_UNAVAILABLE_MSG)
86-
for dep in pkg_resources.working_set:
87-
if dep.project_name == 'docker-py':
90+
for dep in metadata.distributions():
91+
if dep.name == 'docker-py':
8892
raise ImportError(DOCKER_PY_UNAVAILABLE_MSG)
8993

9094
docker_host = os.environ.get('DOCKER_HOST')

shub/utils.py

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
import time
1212

1313
from collections import deque
14-
from six.moves.configparser import SafeConfigParser
15-
from distutils.spawn import find_executable
16-
from distutils.version import LooseVersion, StrictVersion
14+
from configparser import ConfigParser
15+
from shutil import which
16+
from packaging.version import Version
1717
from glob import glob
1818
from importlib import import_module
1919
from tempfile import NamedTemporaryFile, TemporaryFile
@@ -143,17 +143,21 @@ def _check_deploy_files_size(files):
143143

144144
def write_and_echo_logs(keep_log, last_logs, rsp, verbose):
145145
"""It will write logs to temporal file and echo if verbose is True."""
146-
with NamedTemporaryFile(prefix='shub_deploy_', suffix='.log',
147-
delete=(not keep_log)) as log_file:
148-
for line in rsp.iter_lines():
149-
if verbose:
150-
click.echo(line)
151-
last_logs.append(line)
152-
log_file.write(line + b'\n')
146+
log_contents = b""
147+
for line in rsp.iter_lines():
148+
if verbose:
149+
click.echo(line)
150+
last_logs.append(line)
151+
log_contents += line + b'\n'
152+
deployed = _is_deploy_successful(last_logs)
153+
if not deployed:
154+
keep_log = True
155+
echo_short_log_if_deployed(deployed, last_logs, verbose=verbose)
153156

154-
deployed = _is_deploy_successful(last_logs)
155-
echo_short_log_if_deployed(deployed, last_logs, log_file, verbose)
156-
if not log_file.delete:
157+
with NamedTemporaryFile(prefix='shub_deploy_', suffix='.log',
158+
delete=not keep_log) as log_file:
159+
log_file.write(log_contents)
160+
if keep_log:
157161
click.echo("Deploy log location: %s" % log_file.name)
158162
if not deployed:
159163
try:
@@ -163,12 +167,11 @@ def write_and_echo_logs(keep_log, last_logs, rsp, verbose):
163167
raise RemoteErrorException("Deploy failed: {}".format(last_log))
164168

165169

166-
def echo_short_log_if_deployed(deployed, last_logs, log_file, verbose):
170+
def echo_short_log_if_deployed(deployed, last_logs, log_file=None, verbose=False):
167171
if deployed:
168172
if not verbose:
169173
click.echo(last_logs[-1])
170174
else:
171-
log_file.delete = False
172175
if not verbose:
173176
click.echo("Deploy log last %s lines:" % len(last_logs))
174177
for line in last_logs:
@@ -212,7 +215,7 @@ def patch_sys_executable():
212215

213216

214217
def find_exe(exe_name):
215-
exe = find_executable(exe_name)
218+
exe = which(exe_name)
216219
if not exe:
217220
raise NotFoundException("Please install {}".format(exe_name))
218221
return exe
@@ -275,7 +278,7 @@ def pwd_version():
275278

276279

277280
def pwd_git_version():
278-
git = find_executable('git')
281+
git = which('git')
279282
if not git:
280283
return None
281284
try:
@@ -290,7 +293,7 @@ def pwd_git_version():
290293

291294

292295
def pwd_hg_version():
293-
hg = find_executable('hg')
296+
hg = which('hg')
294297
if not hg:
295298
return None
296299
try:
@@ -302,7 +305,7 @@ def pwd_hg_version():
302305

303306

304307
def pwd_bzr_version():
305-
bzr = find_executable('bzr')
308+
bzr = which('bzr')
306309
if not bzr:
307310
return None
308311
try:
@@ -485,9 +488,9 @@ def inside_project():
485488

486489

487490
def get_config(use_closest=True):
488-
"""Get Scrapy config file as a SafeConfigParser"""
491+
"""Get Scrapy config file as a ConfigParser"""
489492
sources = get_sources(use_closest)
490-
cfg = SafeConfigParser()
493+
cfg = ConfigParser()
491494
cfg.read(sources)
492495
return cfg
493496

@@ -506,7 +509,7 @@ def get_sources(use_closest=True):
506509

507510

508511
def get_scrapycfg_targets(cfgfiles=None):
509-
cfg = SafeConfigParser()
512+
cfg = ConfigParser()
510513
cfg.read(cfgfiles or [])
511514
baset = dict(cfg.items('deploy')) if cfg.has_section('deploy') else {}
512515
targets = {}
@@ -627,8 +630,8 @@ def update_available(silent_fail=True):
627630
"""
628631
try:
629632
release_data = latest_github_release()
630-
latest_rls = StrictVersion(release_data['name'].lstrip('v'))
631-
used_rls = StrictVersion(shub.__version__)
633+
latest_rls = Version(release_data['name'].lstrip('v'))
634+
used_rls = Version(shub.__version__)
632635
if used_rls >= latest_rls:
633636
return None
634637
return release_data['html_url']
@@ -643,15 +646,15 @@ def download_from_pypi(dest, pkg=None, reqfile=None, extra_args=None):
643646
if (not pkg and not reqfile) or (pkg and reqfile):
644647
raise ValueError('Call with either pkg or reqfile')
645648
extra_args = extra_args or []
646-
pip_version = LooseVersion(getattr(pip, '__version__', '1.0'))
649+
pip_version = Version(getattr(pip, '__version__', '1.0'))
647650
cmd = 'install'
648651
no_wheel = []
649652
target = [pkg] if pkg else ['-r', reqfile]
650-
if pip_version >= LooseVersion('1.4'):
653+
if pip_version >= Version('1.4'):
651654
no_wheel = ['--no-use-wheel']
652-
if pip_version >= LooseVersion('7'):
655+
if pip_version >= Version('7'):
653656
no_wheel = ['--no-binary=:all:']
654-
if pip_version >= LooseVersion('8'):
657+
if pip_version >= Version('8'):
655658
cmd = 'download'
656659
with patch_sys_executable():
657660
pip_main([cmd, '-d', dest, '--no-deps'] + no_wheel + extra_args +

tests/test_config.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ def test_load(self):
126126
}
127127
self.assertEqual(projects, self.conf.projects)
128128
endpoints = {'external': 'ext_endpoint'}
129-
self.assertDictContainsSubset(endpoints, self.conf.endpoints)
129+
self.assertLessEqual(endpoints.items(), self.conf.endpoints.items())
130130
apikeys = {'default': 'key', 'otheruser': 'otherkey'}
131131
self.assertEqual(apikeys, self.conf.apikeys)
132132
stacks = {'dev': 'scrapy:v1.1'}
@@ -145,7 +145,7 @@ def test_load_partial(self):
145145
"""
146146
conf = self._get_conf_with_yml(yml)
147147
endpoints = {'external': 'ext_endpoint'}
148-
self.assertDictContainsSubset(endpoints, conf.endpoints)
148+
self.assertLessEqual(endpoints.items(), conf.endpoints.items())
149149
self.assertEqual(conf.projects, {})
150150
self.assertEqual(conf.apikeys, {})
151151
self.assertEqual(conf.images, {})
@@ -176,9 +176,9 @@ def test_load_shortcut_mixed(self):
176176
dev: dev_stack
177177
stack: prod_stack
178178
"""
179-
self.assertDictContainsSubset(
180-
self._get_conf_with_yml(yml).stacks,
181-
{'default': 'prod_stack', 'dev': 'dev_stack'},
179+
self.assertLessEqual(
180+
self._get_conf_with_yml(yml).stacks.items(),
181+
{'default': 'prod_stack', 'dev': 'dev_stack'}.items()
182182
)
183183

184184
def test_load_shortcut_conflict(self):
@@ -586,7 +586,7 @@ def test_get_image_ambiguous_global_image_and_global_stack(self):
586586
image: true
587587
stack: scrapy:1.3
588588
""")
589-
with self.assertRaisesRegexp(BadConfigException, '(?i)ambiguous'):
589+
with self.assertRaisesRegex(BadConfigException, '(?i)ambiguous'):
590590
self.conf.get_image('default')
591591

592592
def test_get_image_ambiguous_global_image_and_project_stack(self):
@@ -601,9 +601,9 @@ def test_get_image_ambiguous_global_image_and_project_stack(self):
601601
stack: scrapy:1.3
602602
image: true
603603
""")
604-
with self.assertRaisesRegexp(BadConfigException, '(?i)ambiguous'):
604+
with self.assertRaisesRegex(BadConfigException, '(?i)ambiguous'):
605605
self.conf.get_image('bad')
606-
with self.assertRaisesRegexp(BadConfigException, '(?i)disabled'):
606+
with self.assertRaisesRegex(BadConfigException, '(?i)disabled'):
607607
self.conf.get_image('good')
608608

609609
def test_get_image_ambiguous_project_image_and_project_stack(self):
@@ -614,7 +614,7 @@ def test_get_image_ambiguous_project_image_and_project_stack(self):
614614
image: true
615615
stack: scrapy:1.3
616616
""")
617-
with self.assertRaisesRegexp(BadConfigException, '(?i)ambiguous'):
617+
with self.assertRaisesRegex(BadConfigException, '(?i)ambiguous'):
618618
self.conf.get_image('default')
619619

620620
def test_get_target_conf(self):

tests/test_schedule.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,9 @@ def test_forwards_args_and_settings(self, mock_client):
6363
"--argument ARGWITHEQUAL=val2=val2".split(' '),
6464
)
6565
job_args = mock_proj.jobs.run.call_args[1]['job_args']
66-
self.assertDictContainsSubset(
67-
{'ARG': 'val1', 'ARGWITHEQUAL': 'val2=val2'},
68-
job_args,
66+
self.assertLessEqual(
67+
{'ARG': 'val1', 'ARGWITHEQUAL': 'val2=val2'}.items(),
68+
job_args.items(),
6969
)
7070
job_settings = mock_proj.jobs.run.call_args[1]['job_settings']
7171
self.assertEqual(
@@ -116,9 +116,9 @@ def test_forwards_environment(self, mock_client):
116116
"testspider -e VAR1=VAL1 --environment VAR2=VAL2".split(' '),
117117
)
118118
call_kwargs = mock_proj.jobs.run.call_args[1]
119-
self.assertDictContainsSubset(
120-
{'VAR1': 'VAL1', 'VAR2': 'VAL2'},
121-
call_kwargs['environment'],
119+
self.assertLessEqual(
120+
{'VAR1': 'VAL1', 'VAR2': 'VAL2'}.items(),
121+
call_kwargs['environment'].items(),
122122
)
123123

124124

0 commit comments

Comments
 (0)