diff --git a/examples/cronjob_crud.py b/examples/cronjob_crud.py index 9cbb91d814..1fc4d95081 100644 --- a/examples/cronjob_crud.py +++ b/examples/cronjob_crud.py @@ -1,5 +1,4 @@ #!/usr/bin/python3 -# -*- coding:utf-8 -*- import json import time diff --git a/examples/duration-gep2257.py b/examples/duration-gep2257.py index a7bda1bdb2..f38e06b489 100644 --- a/examples/duration-gep2257.py +++ b/examples/duration-gep2257.py @@ -1,5 +1,4 @@ #!/usr/bin/python3 -# -*- coding:utf-8 -*- """ This example uses kubernetes.utils.duration to parse and display diff --git a/examples/pod_portforward.py b/examples/pod_portforward.py index 13672f181f..d888dec367 100644 --- a/examples/pod_portforward.py +++ b/examples/pod_portforward.py @@ -19,8 +19,7 @@ import select import socket import time - -import six.moves.urllib.request as urllib_request +from urllib import request as urllib_request from kubernetes import config from kubernetes.client import Configuration diff --git a/kubernetes/base/config/exec_provider.py b/kubernetes/base/config/exec_provider.py index 95168f7f6e..84d498e50f 100644 --- a/kubernetes/base/config/exec_provider.py +++ b/kubernetes/base/config/exec_provider.py @@ -19,7 +19,7 @@ from .config_exception import ConfigException -class ExecProvider(object): +class ExecProvider: """ Implementation of the proposal for out-of-tree client authentication providers as described here -- diff --git a/kubernetes/base/config/incluster_config.py b/kubernetes/base/config/incluster_config.py index 86070df43b..fca6d6a1de 100644 --- a/kubernetes/base/config/incluster_config.py +++ b/kubernetes/base/config/incluster_config.py @@ -34,7 +34,7 @@ def _join_host_port(host, port): return template % (host, port) -class InClusterConfigLoader(object): +class InClusterConfigLoader: def __init__(self, token_filename, cert_filename, diff --git a/kubernetes/base/config/kube_config.py b/kubernetes/base/config/kube_config.py index fc88f7f1fa..44b275b3c4 100644 --- a/kubernetes/base/config/kube_config.py +++ b/kubernetes/base/config/kube_config.py @@ -22,14 +22,12 @@ import platform import subprocess import tempfile -import time from collections import namedtuple import oauthlib.oauth2 import urllib3 import yaml from requests_oauthlib import OAuth2Session -from six import PY3 from kubernetes.client import ApiClient, Configuration from kubernetes.config.exec_provider import ExecProvider @@ -85,7 +83,7 @@ def _is_expired(expiry): datetime.datetime.now(tz=UTC)) -class FileOrData(object): +class FileOrData: """Utility class to read content of obj[%data_key_name] or file's content of obj[%file_key_name] and represent it as file or data. Note that the data is preferred. The obj[%file_key_name] will be used iff @@ -151,7 +149,7 @@ def _write_file(self, force_rewrite=False): self._data, self._temp_file_path, force_recreate=force_rewrite) -class CommandTokenSource(object): +class CommandTokenSource: def __init__(self, cmd, args, tokenKey, expiryKey): self._cmd = cmd self._args = args @@ -191,7 +189,7 @@ def token(self): expiry=parse_rfc3339(data['credential']['token_expiry'])) -class KubeConfigLoader(object): +class KubeConfigLoader: def __init__(self, config_dict, active_context=None, get_google_credentials=None, @@ -363,14 +361,9 @@ def _load_oid_token(self, provider): # https://tools.ietf.org/html/rfc7515#appendix-C return - if PY3: - jwt_attributes = json.loads( - base64.urlsafe_b64decode(parts[1] + padding).decode('utf-8') - ) - else: - jwt_attributes = json.loads( - base64.b64decode(parts[1] + padding) - ) + jwt_attributes = json.loads( + base64.urlsafe_b64decode(parts[1] + padding).decode('utf-8') + ) expire = jwt_attributes.get('exp') @@ -392,14 +385,9 @@ def _refresh_oidc(self, provider): if 'idp-certificate-authority-data' in provider['config']: ca_cert = tempfile.NamedTemporaryFile(delete=True) - if PY3: - cert = base64.b64decode( - provider['config']['idp-certificate-authority-data'] - ).decode('utf-8') - else: - cert = base64.b64decode( - provider['config']['idp-certificate-authority-data'] + "==" - ) + cert = base64.b64decode( + provider['config']['idp-certificate-authority-data'] + ).decode('utf-8') with open(ca_cert.name, 'w') as fh: fh.write(cert) @@ -565,7 +553,7 @@ def current_context(self): return self._current_context.value -class ConfigNode(object): +class ConfigNode: """Remembers each config key's path and construct a relevant exception message in case of missing keys. The assumption is all access keys are present in a well-formed kube-config.""" diff --git a/kubernetes/base/config/kube_config_test.py b/kubernetes/base/config/kube_config_test.py index b8063009eb..f860c685b5 100644 --- a/kubernetes/base/config/kube_config_test.py +++ b/kubernetes/base/config/kube_config_test.py @@ -25,7 +25,6 @@ from unittest import mock import yaml -from six import PY3, next from kubernetes.client import Configuration @@ -300,7 +299,7 @@ class TestConfigNode(BaseTestCase): ]} def setUp(self): - super(TestConfigNode, self).setUp() + super().setUp() self.node = ConfigNode("test_obj", self.test_obj) def test_normal_map_array_operations(self): @@ -1274,12 +1273,8 @@ def test_list_kube_config_contexts(self): config_file=config_file) self.assertDictEqual(self.TEST_KUBE_CONFIG['contexts'][0], active_context) - if PY3: - self.assertCountEqual(self.TEST_KUBE_CONFIG['contexts'], - contexts) - else: - self.assertItemsEqual(self.TEST_KUBE_CONFIG['contexts'], - contexts) + self.assertCountEqual(self.TEST_KUBE_CONFIG['contexts'], + contexts) def test_new_client_from_config(self): config_file = self._create_temp_file( diff --git a/kubernetes/base/dynamic/client.py b/kubernetes/base/dynamic/client.py index 64163d7b5c..50b2e5586a 100644 --- a/kubernetes/base/dynamic/client.py +++ b/kubernetes/base/dynamic/client.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import six import json from kubernetes import watch @@ -57,19 +56,15 @@ def inner(self, *args, **kwargs): raise api_exception(e) if serialize_response: try: - if six.PY2: - return serializer(self, json.loads(resp.data)) return serializer(self, json.loads(resp.data.decode('utf8'))) except ValueError: - if six.PY2: - return resp.data return resp.data.decode('utf8') return resp return inner -class DynamicClient(object): +class DynamicClient: """ A kubernetes client that dynamically discovers and interacts with the kubernetes API """ @@ -258,7 +253,7 @@ def request(self, method, path, body=None, **params): local_var_files = {} # Checking Accept header. - new_header_params = dict((key.lower(), value) for key, value in header_params.items()) + new_header_params = {key.lower(): value for key, value in header_params.items()} if not 'accept' in new_header_params: header_params['Accept'] = self.client.select_header_accept([ 'application/json', diff --git a/kubernetes/base/dynamic/discovery.py b/kubernetes/base/dynamic/discovery.py index 3dd28af268..849faf9942 100644 --- a/kubernetes/base/dynamic/discovery.py +++ b/kubernetes/base/dynamic/discovery.py @@ -13,7 +13,6 @@ # limitations under the License. import os -import six import json import logging import hashlib @@ -33,7 +32,7 @@ DISCOVERY_PREFIX = 'apis' -class Discoverer(object): +class Discoverer: """ A convenient container for storing discovered API resources. Allows easy searching and retrieval of specific resources. @@ -43,9 +42,7 @@ class Discoverer(object): def __init__(self, client, cache_file): self.client = client - default_cache_id = self.client.configuration.host - if six.PY3: - default_cache_id = default_cache_id.encode('utf-8') + default_cache_id = self.client.configuration.host.encode('utf-8') try: default_cachefile_name = 'osrcp-{0}.json'.format(hashlib.md5(default_cache_id, usedforsecurity=False).hexdigest()) except TypeError: @@ -60,7 +57,7 @@ def __init_cache(self, refresh=False): refresh = True else: try: - with open(self.__cache_file, 'r') as f: + with open(self.__cache_file) as f: self._cache = json.load(f, cls=partial(CacheDecoder, self.client)) if self._cache.get('library_version') != __version__: # Version mismatch, need to refresh cache @@ -313,7 +310,7 @@ def __iter__(self): prefix, group, version, rg.preferred) self._cache['resources'][prefix][group][version] = rg self.__update_cache = True - for _, resource in six.iteritems(rg.resources): + for _, resource in rg.resources.items(): yield resource self.__maybe_write_cache() @@ -397,7 +394,7 @@ def __iter__(self): yield resource -class ResourceGroup(object): +class ResourceGroup: """Helper class for Discoverer container""" def __init__(self, preferred, resources=None): self.preferred = preferred diff --git a/kubernetes/base/dynamic/resource.py b/kubernetes/base/dynamic/resource.py index 58a60ec402..d77ec8e403 100644 --- a/kubernetes/base/dynamic/resource.py +++ b/kubernetes/base/dynamic/resource.py @@ -19,7 +19,7 @@ from pprint import pformat -class Resource(object): +class Resource: """ Represents an API resource type, containing the information required to build urls for requests """ def __init__(self, prefix=None, group=None, api_version=None, kind=None, @@ -278,7 +278,7 @@ def to_dict(self): return d -class ResourceInstance(object): +class ResourceInstance: """ A parsed instance of an API resource. It exists solely to ease interaction with API objects by allowing attributes to be accessed with '.' notation. @@ -338,14 +338,14 @@ def __repr__(self): def __getattr__(self, name): if not '_ResourceInstance__initialised' in self.__dict__: - return super(ResourceInstance, self).__getattr__(name) + return super().__getattr__(name) return getattr(self.attributes, name) def __setattr__(self, name, value): if not '_ResourceInstance__initialised' in self.__dict__: - return super(ResourceInstance, self).__setattr__(name, value) + return super().__setattr__(name, value) elif name in self.__dict__: - return super(ResourceInstance, self).__setattr__(name, value) + return super().__setattr__(name, value) else: self.attributes[name] = value @@ -359,7 +359,7 @@ def __dir__(self): return dir(type(self)) + list(self.attributes.__dict__.keys()) -class ResourceField(object): +class ResourceField: """ A parsed instance of an API resource attribute. It exists solely to ease interaction with API objects by allowing attributes to be accessed with '.' notation @@ -389,8 +389,7 @@ def __dir__(self): return dir(type(self)) + list(self.__dict__.keys()) def __iter__(self): - for k, v in self.__dict__.items(): - yield (k, v) + yield from self.__dict__.items() def to_dict(self): return self.__serialize(self) diff --git a/kubernetes/base/hack/boilerplate/boilerplate.py b/kubernetes/base/hack/boilerplate/boilerplate.py index eec04b4583..e58c625144 100755 --- a/kubernetes/base/hack/boilerplate/boilerplate.py +++ b/kubernetes/base/hack/boilerplate/boilerplate.py @@ -14,7 +14,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import print_function import argparse import datetime @@ -60,7 +59,7 @@ def get_refs(): args.boilerplate_dir, "boilerplate.*.txt")): extension = os.path.basename(path).split(".")[1] - ref_file = open(path, 'r') + ref_file = open(path) ref = ref_file.read().splitlines() ref_file.close() refs[extension] = ref @@ -70,7 +69,7 @@ def get_refs(): def file_passes(filename, refs, regexs): try: - f = open(filename, 'r') + f = open(filename) except Exception as exc: print("Unable to open %s: %s" % (filename, exc), file=verbose_out) return False @@ -172,7 +171,7 @@ def get_files(extensions): def get_dates(): years = datetime.datetime.now().year - return '(%s)' % '|'.join((str(year) for year in range(2014, years+1))) + return '(%s)' % '|'.join(str(year) for year in range(2014, years+1)) def get_regexs(): diff --git a/kubernetes/base/leaderelection/leaderelection.py b/kubernetes/base/leaderelection/leaderelection.py index bbeb813505..4fff8d0790 100644 --- a/kubernetes/base/leaderelection/leaderelection.py +++ b/kubernetes/base/leaderelection/leaderelection.py @@ -19,16 +19,13 @@ import threading from .leaderelectionrecord import LeaderElectionRecord import logging -# if condition to be removed when support for python2 will be removed -if sys.version_info > (3, 0): - from http import HTTPStatus -else: - import httplib +from http import HTTPStatus + logger = logging.getLogger("leaderelection") """ This package implements leader election using an annotation in a Kubernetes object. -The onstarted_leading function is run in a thread and when it returns, if it does +The onstarted_leading function is run in a thread and when it returns, if it does it might not be safe to run it again in a process. At first all candidates are considered followers. The one to create a lock or update @@ -118,17 +115,13 @@ def try_acquire_or_renew(self): # A lock is not created with that name, try to create one if not lock_status: - # To be removed when support for python2 will be removed - if sys.version_info > (3, 0): - if json.loads(old_election_record.body)['code'] != HTTPStatus.NOT_FOUND: - logger.info("Error retrieving resource lock {} as {}".format(self.election_config.lock.name, - old_election_record.reason)) - return False - else: - if json.loads(old_election_record.body)['code'] != httplib.NOT_FOUND: - logger.info("Error retrieving resource lock {} as {}".format(self.election_config.lock.name, - old_election_record.reason)) - return False + if json.loads(old_election_record.body)[ + 'code'] != HTTPStatus.NOT_FOUND: + logger.info( + "Error retrieving resource lock {} as {}".format( + self.election_config.lock.name, + old_election_record.reason)) + return False logger.info("{} is trying to create a lock".format(leader_election_record.holder_identity)) create_status = self.election_config.lock.create(name=self.election_config.lock.name, diff --git a/kubernetes/base/stream/ws_client.py b/kubernetes/base/stream/ws_client.py index 27191fa93b..3031e01560 100644 --- a/kubernetes/base/stream/ws_client.py +++ b/kubernetes/base/stream/ws_client.py @@ -22,16 +22,12 @@ import ssl import threading import time - -import six -import yaml - - -from six.moves.urllib.parse import urlencode, urlparse, urlunparse -from six import StringIO, BytesIO - +from urllib.parse import urlencode, urlparse, urlunparse +from io import StringIO, BytesIO from websocket import WebSocket, ABNF, enableTrace, WebSocketConnectionClosedException from base64 import urlsafe_b64decode + +import yaml from requests.utils import should_bypass_proxies STDIN_CHANNEL = 0 @@ -136,12 +132,12 @@ def readline_channel(self, channel, timeout=None): def write_channel(self, channel, data): """Write data to a channel.""" # check if we're writing binary data or not - binary = six.PY3 and type(data) == six.binary_type + binary = type(data) == bytes opcode = ABNF.OPCODE_BINARY if binary else ABNF.OPCODE_TEXT channel_prefix = chr(channel) if binary: - channel_prefix = six.binary_type(channel_prefix, "ascii") + channel_prefix = bytes(channel_prefix, "ascii") payload = channel_prefix + data self.sock.send(payload, opcode=opcode) @@ -356,7 +352,7 @@ def __init__(self, ix, port_number): # The remote port number self.port_number = port_number # The websocket channel byte number for this port - self.channel = six.int2byte(ix * 2) + self.channel = bytes((ix * 2,)) # A socket pair is created to provide a means of translating the data flow # between the python application and the kubernetes websocket. The self.python # half of the socket pair is used by the _proxy method to receive and send data @@ -441,7 +437,7 @@ def _proxy(self): if opcode == ABNF.OPCODE_BINARY: if not frame.data: raise RuntimeError("Unexpected frame data size") - channel = six.byte2int(frame.data) + channel = frame.data[0] if channel >= len(channel_ports): raise RuntimeError("Unexpected channel number: %s" % channel) port = channel_ports[channel] @@ -458,7 +454,7 @@ def _proxy(self): raise RuntimeError( "Unexpected initial channel frame data size" ) - port_number = six.byte2int(frame.data[1:2]) + (six.byte2int(frame.data[2:3]) * 256) + port_number = frame.data[1:2][0] + (frame.data[2:3][0] * 256) if port_number != port.port_number: raise RuntimeError( "Unexpected port number in initial channel frame: %s" % port_number diff --git a/kubernetes/base/tox.ini b/kubernetes/base/tox.ini index 37a188f127..91cbf9b555 100644 --- a/kubernetes/base/tox.ini +++ b/kubernetes/base/tox.ini @@ -1,13 +1,21 @@ [tox] skipsdist = True envlist = - py3{5,6,7,8,9} - py3{5,6,7,8,9}-functional + py3{8,9} + py31{0,1,2,3,4,5} + py3{8,9}-functional + py31{0,1,2,3,4,5}-functional [testenv] -passenv = TOXENV CI TRAVIS TRAVIS_* +passenv = + TOXENV + CI + TRAVIS + TRAVIS_* +deps = + pytest commands = python -V - pip install pytest + pytest ./run_tox.sh pytest diff --git a/kubernetes/base/watch/watch.py b/kubernetes/base/watch/watch.py index 71926295cc..0e39e191a9 100644 --- a/kubernetes/base/watch/watch.py +++ b/kubernetes/base/watch/watch.py @@ -12,9 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +import http import json import pydoc -import sys from kubernetes import client @@ -28,14 +28,7 @@ # provide return_type to Watch class's __init__. TYPE_LIST_SUFFIX = "List" - -PY2 = sys.version_info[0] == 2 -if PY2: - import httplib - HTTP_STATUS_GONE = httplib.GONE -else: - import http - HTTP_STATUS_GONE = http.HTTPStatus.GONE +HTTP_STATUS_GONE = http.HTTPStatus.GONE class SimpleNamespace: @@ -83,7 +76,7 @@ def iter_resp_lines(resp): next_newline = buffer.find(b'\n') -class Watch(object): +class Watch: def __init__(self, return_type=None): self._raw_return_type = return_type diff --git a/kubernetes/e2e_test/__init__.py b/kubernetes/e2e_test/__init__.py index 19f5e722fb..0680747644 100644 --- a/kubernetes/e2e_test/__init__.py +++ b/kubernetes/e2e_test/__init__.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at diff --git a/kubernetes/e2e_test/test_apps.py b/kubernetes/e2e_test/test_apps.py index 2735ed3cba..f4f333a055 100644 --- a/kubernetes/e2e_test/test_apps.py +++ b/kubernetes/e2e_test/test_apps.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at diff --git a/kubernetes/e2e_test/test_batch.py b/kubernetes/e2e_test/test_batch.py index 9935df3135..bb2bbb63c7 100644 --- a/kubernetes/e2e_test/test_batch.py +++ b/kubernetes/e2e_test/test_batch.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at diff --git a/kubernetes/e2e_test/test_client.py b/kubernetes/e2e_test/test_client.py index 6d2972f2bc..7f6c80559e 100644 --- a/kubernetes/e2e_test/test_client.py +++ b/kubernetes/e2e_test/test_client.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at @@ -19,9 +17,10 @@ import time import unittest import uuid -import six import io import gzip +import urllib.request as urllib_request +from http import HTTPStatus from kubernetes.client import api_client from kubernetes.client.api import core_v1_api @@ -30,12 +29,6 @@ from kubernetes.stream.ws_client import ERROR_CHANNEL from kubernetes.client.rest import ApiException -import six.moves.urllib.request as urllib_request - -if six.PY3: - from http import HTTPStatus -else: - import httplib def short_uuid(): @@ -88,8 +81,7 @@ def test_pod_apis(self): resp = api.read_namespaced_service_account(name='default', namespace='default') except ApiException as e: - if (six.PY3 and e.status != HTTPStatus.NOT_FOUND) or ( - six.PY3 is False and e.status != httplib.NOT_FOUND): + if e.status != HTTPStatus.NOT_FOUND: print('error: %s' % e) self.fail( msg="unexpected error getting default service account") @@ -274,8 +266,7 @@ def test_exit_code(self): resp = api.read_namespaced_service_account(name='default', namespace='default') except ApiException as e: - if (six.PY3 and e.status != HTTPStatus.NOT_FOUND) or ( - six.PY3 is False and e.status != httplib.NOT_FOUND): + if e.status != HTTPStatus.NOT_FOUND: print('error: %s' % e) self.fail( msg="unexpected error getting default service account") diff --git a/kubernetes/e2e_test/test_utils.py b/kubernetes/e2e_test/test_utils.py index b06c0a6b4a..433089efa1 100644 --- a/kubernetes/e2e_test/test_utils.py +++ b/kubernetes/e2e_test/test_utils.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at diff --git a/kubernetes/e2e_test/test_watch.py b/kubernetes/e2e_test/test_watch.py index 134e9c26fd..6879d6d350 100644 --- a/kubernetes/e2e_test/test_watch.py +++ b/kubernetes/e2e_test/test_watch.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at diff --git a/kubernetes/utils/__init__.py b/kubernetes/utils/__init__.py index c83d54fe76..92ab696782 100644 --- a/kubernetes/utils/__init__.py +++ b/kubernetes/utils/__init__.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import absolute_import from .create_from_yaml import (FailToCreateError, create_from_dict, create_from_yaml, create_from_directory)