Merging upstream version 1.4.0+dfsg.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
295cee048d
commit
c306a1dcc2
10 changed files with 509 additions and 495 deletions
4
Makefile
4
Makefile
|
@ -49,7 +49,9 @@ coverage_report:
|
||||||
|
|
||||||
pep8:
|
pep8:
|
||||||
-pep8 -r --max-line-length=120 --ignore=$(PEP8_IGNORE) cvprac/
|
-pep8 -r --max-line-length=120 --ignore=$(PEP8_IGNORE) cvprac/
|
||||||
-pep8 -r --max-line-length=120 --ignore=$(PEP8_IGNORE),E402 test/
|
-pep8 -r --max-line-length=120 --ignore=$(PEP8_IGNORE),E402 test/lib/
|
||||||
|
-pep8 -r --max-line-length=120 --ignore=$(PEP8_IGNORE),E402 test/system/
|
||||||
|
-pep8 -r --ignore=$(PEP8_IGNORE),E402,E501 test/unit/
|
||||||
|
|
||||||
pyflakes:
|
pyflakes:
|
||||||
pyflakes cvprac/ test/
|
pyflakes cvprac/ test/
|
||||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
1.3.2
|
1.4.0
|
||||||
|
|
|
@ -32,5 +32,5 @@
|
||||||
''' RESTful API Client class for Cloudvision(R) Portal
|
''' RESTful API Client class for Cloudvision(R) Portal
|
||||||
'''
|
'''
|
||||||
|
|
||||||
__version__ = '1.3.2'
|
__version__ = '1.4.0'
|
||||||
__author__ = 'Arista Networks, Inc.'
|
__author__ = 'Arista Networks, Inc.'
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -29,6 +29,9 @@
|
||||||
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
|
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
|
||||||
# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
# pylint: disable=too-many-branches,too-many-statements,too-many-locals,too-many-lines
|
||||||
|
|
||||||
''' RESTful API Client class for Cloudvision(R) Portal
|
''' RESTful API Client class for Cloudvision(R) Portal
|
||||||
|
|
||||||
This module provides a RESTful API client for Cloudvision(R) Portal (CVP)
|
This module provides a RESTful API client for Cloudvision(R) Portal (CVP)
|
||||||
|
@ -96,18 +99,24 @@ import json
|
||||||
import logging
|
import logging
|
||||||
from logging.handlers import SysLogHandler
|
from logging.handlers import SysLogHandler
|
||||||
from itertools import cycle
|
from itertools import cycle
|
||||||
from pkg_resources import parse_version
|
from packaging.version import parse
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from requests.exceptions import ConnectionError, HTTPError, Timeout, \
|
from requests.exceptions import ( # pylint: disable=redefined-builtin
|
||||||
ReadTimeout, TooManyRedirects, JSONDecodeError
|
ConnectionError,
|
||||||
|
HTTPError,
|
||||||
|
Timeout,
|
||||||
|
ReadTimeout,
|
||||||
|
TooManyRedirects,
|
||||||
|
JSONDecodeError
|
||||||
|
)
|
||||||
|
|
||||||
from cvprac.cvp_api import CvpApi
|
from cvprac.cvp_api import CvpApi
|
||||||
from cvprac.cvp_client_errors import CvpApiError, CvpLoginError, \
|
from cvprac.cvp_client_errors import CvpApiError, CvpLoginError, \
|
||||||
CvpRequestError, CvpSessionLogOutError
|
CvpRequestError, CvpSessionLogOutError
|
||||||
|
|
||||||
|
|
||||||
class CvpClient(object):
|
class CvpClient():
|
||||||
''' Use this class to create a persistent connection to CVP.
|
''' Use this class to create a persistent connection to CVP.
|
||||||
'''
|
'''
|
||||||
# pylint: disable=too-many-instance-attributes
|
# pylint: disable=too-many-instance-attributes
|
||||||
|
@ -233,28 +242,37 @@ class CvpClient(object):
|
||||||
' Appending 0. Updated Version String - %s',
|
' Appending 0. Updated Version String - %s',
|
||||||
".".join(version_components))
|
".".join(version_components))
|
||||||
full_version = ".".join(version_components)
|
full_version = ".".join(version_components)
|
||||||
if parse_version(full_version) >= parse_version('2023.1.0'):
|
if parse(full_version) >= parse('2024.1.0'):
|
||||||
|
self.log.info('Setting API version to v12')
|
||||||
|
self.apiversion = 12.0
|
||||||
|
elif parse(full_version) >= parse('2023.3.0'):
|
||||||
|
self.log.info('Setting API version to v11')
|
||||||
|
self.apiversion = 11.0
|
||||||
|
elif parse(full_version) >= parse('2023.2.0'):
|
||||||
|
self.log.info('Setting API version to v10')
|
||||||
|
self.apiversion = 10.0
|
||||||
|
elif parse(full_version) >= parse('2023.1.0'):
|
||||||
self.log.info('Setting API version to v9')
|
self.log.info('Setting API version to v9')
|
||||||
self.apiversion = 9.0
|
self.apiversion = 9.0
|
||||||
elif parse_version(full_version) >= parse_version('2022.1.0'):
|
elif parse(full_version) >= parse('2022.1.0'):
|
||||||
self.log.info('Setting API version to v8')
|
self.log.info('Setting API version to v8')
|
||||||
self.apiversion = 8.0
|
self.apiversion = 8.0
|
||||||
elif parse_version(full_version) >= parse_version('2021.3.0'):
|
elif parse(full_version) >= parse('2021.3.0'):
|
||||||
self.log.info('Setting API version to v7')
|
self.log.info('Setting API version to v7')
|
||||||
self.apiversion = 7.0
|
self.apiversion = 7.0
|
||||||
elif parse_version(full_version) >= parse_version('2021.2.0'):
|
elif parse(full_version) >= parse('2021.2.0'):
|
||||||
self.log.info('Setting API version to v6')
|
self.log.info('Setting API version to v6')
|
||||||
self.apiversion = 6.0
|
self.apiversion = 6.0
|
||||||
elif parse_version(full_version) >= parse_version('2020.2.4'):
|
elif parse(full_version) >= parse('2020.2.4'):
|
||||||
self.log.info('Setting API version to v5')
|
self.log.info('Setting API version to v5')
|
||||||
self.apiversion = 5.0
|
self.apiversion = 5.0
|
||||||
elif parse_version(full_version) >= parse_version('2020.1.1'):
|
elif parse(full_version) >= parse('2020.1.1'):
|
||||||
self.log.info('Setting API version to v4')
|
self.log.info('Setting API version to v4')
|
||||||
self.apiversion = 4.0
|
self.apiversion = 4.0
|
||||||
elif parse_version(full_version) >= parse_version('2019.0.0'):
|
elif parse(full_version) >= parse('2019.0.0'):
|
||||||
self.log.info('Setting API version to v3')
|
self.log.info('Setting API version to v3')
|
||||||
self.apiversion = 3.0
|
self.apiversion = 3.0
|
||||||
elif parse_version(full_version) >= parse_version('2018.2.0'):
|
elif parse(full_version) >= parse('2018.2.0'):
|
||||||
self.log.info('Setting API version to v2')
|
self.log.info('Setting API version to v2')
|
||||||
self.apiversion = 2.0
|
self.apiversion = 2.0
|
||||||
else:
|
else:
|
||||||
|
@ -378,13 +396,12 @@ class CvpClient(object):
|
||||||
self.error_msg = '\n'
|
self.error_msg = '\n'
|
||||||
for _ in range(0, num_nodes):
|
for _ in range(0, num_nodes):
|
||||||
host = next(self.node_pool)
|
host = next(self.node_pool)
|
||||||
self.url_prefix = ('https://%s:%d/web' % (host, self.port or 443))
|
self.url_prefix = f"https://{host}:{self.port or 443}/web"
|
||||||
self.url_prefix_short = ('https://%s:%d'
|
self.url_prefix_short = f"https://{host}:{self.port or 443}"
|
||||||
% (host, self.port or 443))
|
|
||||||
error = self._reset_session()
|
error = self._reset_session()
|
||||||
if error is None:
|
if error is None:
|
||||||
break
|
break
|
||||||
self.error_msg += '%s: %s\n' % (host, error)
|
self.error_msg += f"{host}: {error}\n"
|
||||||
|
|
||||||
def _reset_session(self):
|
def _reset_session(self):
|
||||||
''' Get a new request session and try logging into the current
|
''' Get a new request session and try logging into the current
|
||||||
|
@ -428,23 +445,20 @@ class CvpClient(object):
|
||||||
if 'Unauthorized' in response.reason:
|
if 'Unauthorized' in response.reason:
|
||||||
# Check for 'Unauthorized' User error because this is how
|
# Check for 'Unauthorized' User error because this is how
|
||||||
# CVP responds to a logged out users requests in 2018.x.
|
# CVP responds to a logged out users requests in 2018.x.
|
||||||
msg = '%s: Request Error: %s' % (prefix, response.reason)
|
msg = f"{prefix}: Request Error: {response.reason}"
|
||||||
self.log.error(msg)
|
self.log.error(msg)
|
||||||
raise CvpApiError(msg)
|
raise CvpApiError(msg)
|
||||||
if 'User is unauthorized' in response.text:
|
if 'User is unauthorized' in response.text:
|
||||||
# Check for 'User is unauthorized' response text because this
|
# Check for 'User is unauthorized' response text because this
|
||||||
# is how CVP responds to a logged out users requests in 2019.x.
|
# is how CVP responds to a logged out users requests in 2019.x.
|
||||||
msg = '%s: Request Error: User is unauthorized' % prefix
|
msg = f"{prefix}: Request Error: User is unauthorized"
|
||||||
self.log.error(msg)
|
self.log.error(msg)
|
||||||
raise CvpApiError(msg)
|
raise CvpApiError(msg)
|
||||||
else:
|
msg = f"{prefix}: Request Error: {response.reason} - {response.text}"
|
||||||
msg = '%s: Request Error: %s - %s' % (prefix, response.reason,
|
|
||||||
response.text)
|
|
||||||
self.log.error(msg)
|
|
||||||
raise CvpRequestError(msg)
|
raise CvpRequestError(msg)
|
||||||
|
|
||||||
if 'LOG OUT MESSAGE' in response.text:
|
if 'LOG OUT MESSAGE' in response.text:
|
||||||
msg = ('%s: Request Error: session logged out' % prefix)
|
msg = f"{prefix}: Request Error: session logged out"
|
||||||
raise CvpSessionLogOutError(msg)
|
raise CvpSessionLogOutError(msg)
|
||||||
|
|
||||||
joutput = json_decoder(response.text)
|
joutput = json_decoder(response.text)
|
||||||
|
@ -460,9 +474,9 @@ class CvpClient(object):
|
||||||
# Build the error message from all the errors.
|
# Build the error message from all the errors.
|
||||||
err_msg = error_list[0]
|
err_msg = error_list[0]
|
||||||
for idx in range(1, len(error_list)):
|
for idx in range(1, len(error_list)):
|
||||||
err_msg = '%s\n%s' % (err_msg, error_list[idx])
|
err_msg = f"{err_msg}\n{error_list[idx]}"
|
||||||
|
|
||||||
msg = ('%s: Request Error: %s' % (prefix, err_msg))
|
msg = f"{prefix}: Request Error: {err_msg}"
|
||||||
self.log.error(msg)
|
self.log.error(msg)
|
||||||
raise CvpApiError(msg)
|
raise CvpApiError(msg)
|
||||||
|
|
||||||
|
@ -477,8 +491,7 @@ class CvpClient(object):
|
||||||
response status is not OK.
|
response status is not OK.
|
||||||
'''
|
'''
|
||||||
if not response.ok:
|
if not response.ok:
|
||||||
msg = '%s: Request Error: %s - %s' % (prefix, response.reason,
|
msg = f"{prefix}: Request Error: {response.reason} - {response.text}"
|
||||||
response.text)
|
|
||||||
self.log.error(msg)
|
self.log.error(msg)
|
||||||
raise CvpRequestError(msg)
|
raise CvpRequestError(msg)
|
||||||
|
|
||||||
|
@ -512,7 +525,7 @@ class CvpClient(object):
|
||||||
self.headers.pop('APP_SESSION_ID', None)
|
self.headers.pop('APP_SESSION_ID', None)
|
||||||
if self.api_token is not None:
|
if self.api_token is not None:
|
||||||
return self._set_headers_api_token()
|
return self._set_headers_api_token()
|
||||||
elif self.is_cvaas:
|
if self.is_cvaas:
|
||||||
raise CvpLoginError('CVaaS only supports API token authentication.'
|
raise CvpLoginError('CVaaS only supports API token authentication.'
|
||||||
' Please create an API token and provide it'
|
' Please create an API token and provide it'
|
||||||
' via the api_token parameter in combination'
|
' via the api_token parameter in combination'
|
||||||
|
@ -551,7 +564,7 @@ class CvpClient(object):
|
||||||
headers=self.headers,
|
headers=self.headers,
|
||||||
timeout=self.connect_timeout,
|
timeout=self.connect_timeout,
|
||||||
verify=self.cert)
|
verify=self.cert)
|
||||||
self._is_good_response(response, 'Authenticate: %s' % url)
|
self._is_good_response(response, f"Authenticate: {url}")
|
||||||
|
|
||||||
self.cookies = response.cookies
|
self.cookies = response.cookies
|
||||||
self.headers['APP_SESSION_ID'] = response.json()['sessionId']
|
self.headers['APP_SESSION_ID'] = response.json()['sessionId']
|
||||||
|
@ -561,18 +574,20 @@ class CvpClient(object):
|
||||||
'''
|
'''
|
||||||
# If using an API token there is no need to run a Login API.
|
# If using an API token there is no need to run a Login API.
|
||||||
# Simply add the token into the headers or cookies
|
# Simply add the token into the headers or cookies
|
||||||
self.headers['Authorization'] = 'Bearer %s' % self.api_token
|
self.headers['Authorization'] = f"Bearer {self.api_token}"
|
||||||
# Alternative to adding token to headers it can be added to
|
# Alternative to adding token to headers it can be added to
|
||||||
# cookies as shown below.
|
# cookies as shown below.
|
||||||
# self.cookies = {'access_token': self.api_token}
|
# self.cookies = {'access_token': self.api_token}
|
||||||
url = self.url_prefix_short + '/api/v1/rest/'
|
url = self.url_prefix_short + '/api/v1/rest/'
|
||||||
response = self.session.get(url,
|
response = self.session.get(
|
||||||
|
url,
|
||||||
cookies=self.cookies,
|
cookies=self.cookies,
|
||||||
headers=self.headers,
|
headers=self.headers,
|
||||||
timeout=self.connect_timeout,
|
timeout=self.connect_timeout,
|
||||||
verify=self.cert)
|
verify=self.cert
|
||||||
|
)
|
||||||
# Verify that the generic request was successful
|
# Verify that the generic request was successful
|
||||||
self._is_good_response(response, 'Authenticate: %s' % url)
|
self._is_good_response(response, f"Authenticate: {url}")
|
||||||
|
|
||||||
def logout(self):
|
def logout(self):
|
||||||
'''
|
'''
|
||||||
|
@ -584,7 +599,7 @@ class CvpClient(object):
|
||||||
self.log.info('User logged out.')
|
self.log.info('User logged out.')
|
||||||
self.session = None
|
self.session = None
|
||||||
else:
|
else:
|
||||||
err = 'Error trying to logout %s' % response
|
err = f"Error trying to logout {response}"
|
||||||
self.log.error(err)
|
self.log.error(err)
|
||||||
|
|
||||||
def _make_request(self, req_type, url, timeout, data=None,
|
def _make_request(self, req_type, url, timeout, data=None,
|
||||||
|
@ -700,8 +715,8 @@ class CvpClient(object):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
resp_data = response.json()
|
resp_data = response.json()
|
||||||
if (resp_data is not None and 'result' in resp_data
|
if (resp_data is not None and 'result' in resp_data and
|
||||||
and '/resources/' in full_url):
|
'/resources/' in full_url):
|
||||||
# Resource APIs use JSON streaming and will return
|
# Resource APIs use JSON streaming and will return
|
||||||
# multiple JSON objects during GetAll type API
|
# multiple JSON objects during GetAll type API
|
||||||
# calls. We are wrapping the multiple objects into
|
# calls. We are wrapping the multiple objects into
|
||||||
|
@ -725,9 +740,7 @@ class CvpClient(object):
|
||||||
' response data. Attempt to decode')
|
' response data. Attempt to decode')
|
||||||
decoded_data = json_decoder(response.text)
|
decoded_data = json_decoder(response.text)
|
||||||
return {'data': decoded_data}
|
return {'data': decoded_data}
|
||||||
else:
|
self.log.error("Unknown format for JSONDecodeError - %s", err_str)
|
||||||
self.log.error('Unknown format for JSONDecodeError - %s',
|
|
||||||
err_str)
|
|
||||||
raise error
|
raise error
|
||||||
|
|
||||||
def _send_request(self, req_type, full_url, timeout, data=None,
|
def _send_request(self, req_type, full_url, timeout, data=None,
|
||||||
|
@ -795,7 +808,7 @@ class CvpClient(object):
|
||||||
timeout=timeout,
|
timeout=timeout,
|
||||||
verify=self.cert)
|
verify=self.cert)
|
||||||
else:
|
else:
|
||||||
fhs = dict()
|
fhs = {}
|
||||||
fhs['Accept'] = self.headers['Accept']
|
fhs['Accept'] = self.headers['Accept']
|
||||||
if 'APP_SESSION_ID' in self.headers:
|
if 'APP_SESSION_ID' in self.headers:
|
||||||
fhs['APP_SESSION_ID'] = self.headers[
|
fhs['APP_SESSION_ID'] = self.headers[
|
||||||
|
@ -830,8 +843,7 @@ class CvpClient(object):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._is_good_response(response, '%s: %s ' %
|
self._is_good_response(response, f"{req_type}: {full_url} ")
|
||||||
(req_type, full_url))
|
|
||||||
except CvpSessionLogOutError as error:
|
except CvpSessionLogOutError as error:
|
||||||
self.log.debug(error)
|
self.log.debug(error)
|
||||||
# Retry the request to the same node if there was a CVP session
|
# Retry the request to the same node if there was a CVP session
|
||||||
|
@ -840,7 +852,6 @@ class CvpClient(object):
|
||||||
# be retried on the same node.
|
# be retried on the same node.
|
||||||
if req_try + 1 == self.NUM_RETRY_REQUESTS:
|
if req_try + 1 == self.NUM_RETRY_REQUESTS:
|
||||||
raise error
|
raise error
|
||||||
else:
|
|
||||||
self._reset_session()
|
self._reset_session()
|
||||||
if not self.session:
|
if not self.session:
|
||||||
raise error
|
raise error
|
||||||
|
@ -859,12 +870,10 @@ class CvpClient(object):
|
||||||
# will be retried on the same node.
|
# will be retried on the same node.
|
||||||
if req_try + 1 == self.NUM_RETRY_REQUESTS:
|
if req_try + 1 == self.NUM_RETRY_REQUESTS:
|
||||||
raise error
|
raise error
|
||||||
else:
|
|
||||||
self._reset_session()
|
self._reset_session()
|
||||||
if not self.session:
|
if not self.session:
|
||||||
raise error
|
raise error
|
||||||
continue
|
continue
|
||||||
else:
|
|
||||||
# pylint: disable=raising-bad-type
|
# pylint: disable=raising-bad-type
|
||||||
raise error
|
raise error
|
||||||
return response
|
return response
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
check-manifest
|
check-manifest
|
||||||
coverage
|
coverage
|
||||||
mock
|
|
||||||
pdoc
|
pdoc
|
||||||
pep8
|
pep8
|
||||||
pyflakes
|
pyflakes
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
import argparse
|
import argparse
|
||||||
import ssl
|
import ssl
|
||||||
import sys
|
import sys
|
||||||
from pkg_resources import parse_version
|
from packaging.version import parse
|
||||||
from getpass import getpass
|
from getpass import getpass
|
||||||
from cvprac.cvp_client import CvpClient
|
from cvprac.cvp_client import CvpClient
|
||||||
import requests.packages.urllib3
|
import requests.packages.urllib3
|
||||||
|
@ -56,7 +56,7 @@ def main():
|
||||||
|
|
||||||
# Get the current CVP version
|
# Get the current CVP version
|
||||||
cvp_release = clnt.api.get_cvp_info()['version']
|
cvp_release = clnt.api.get_cvp_info()['version']
|
||||||
if parse_version(cvp_release) < parse_version('2020.3.0'):
|
if parse(cvp_release) < parse('2020.3.0'):
|
||||||
# For older CVP, we manually trigger a compliance check
|
# For older CVP, we manually trigger a compliance check
|
||||||
try:
|
try:
|
||||||
clnt.api.check_compliance('root', 'container')
|
clnt.api.check_compliance('root', 'container')
|
||||||
|
|
20
docs/release-notes-1.4.0.rst
Normal file
20
docs/release-notes-1.4.0.rst
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
######
|
||||||
|
v1.4.0
|
||||||
|
######
|
||||||
|
|
||||||
|
2024-5-6
|
||||||
|
|
||||||
|
Enhancements
|
||||||
|
^^^^^^^^^^^^
|
||||||
|
|
||||||
|
* Move from pkg_resources to packaging for Python 3.12 support. (`271 <https://github.com/aristanetworks/cvprac/pull/271>`_) [`mharista <https://github.com/mharista>`_]
|
||||||
|
* Add support for searchTopology V3 endpoint. (`275 <https://github.com/aristanetworks/cvprac/pull/275>`_) [`mharista <https://github.com/mharista>`_]
|
||||||
|
|
||||||
|
Fixed
|
||||||
|
^^^^^
|
||||||
|
|
||||||
|
* Add missing url encoding for get_user. (`264 <https://github.com/aristanetworks/cvprac/pull/264>`_) [`noredistribution <https://github.com/noredistribution>`_]
|
||||||
|
* Updated the enrollment endpoint for CVaaS. (`269 <https://github.com/aristanetworks/cvprac/pull/269>`_) [`noredistribution <https://github.com/noredistribution>`_]
|
||||||
|
* Add timeout to get_configlets_and_mappers(). (`270 <https://github.com/aristanetworks/cvprac/pull/270>`_) [`noredistribution <https://github.com/noredistribution>`_]
|
||||||
|
* Update setup.py to reference python3 only. (`272 <https://github.com/aristanetworks/cvprac/pull/272>`_) [`mharista <https://github.com/mharista>`_]
|
||||||
|
* Python3 lint/format fixes. (`273 <https://github.com/aristanetworks/cvprac/pull/273>`_) [`mharista <https://github.com/mharista>`_]
|
|
@ -1 +1,2 @@
|
||||||
requests[socks]>=2.27.0
|
requests[socks]>=2.27.0
|
||||||
|
packaging>=23.2
|
||||||
|
|
11
setup.py
11
setup.py
|
@ -100,8 +100,13 @@ setup(
|
||||||
|
|
||||||
# Specify the Python versions you support here. In particular, ensure
|
# Specify the Python versions you support here. In particular, ensure
|
||||||
# that you indicate whether you support Python 2, Python 3 or both.
|
# that you indicate whether you support Python 2, Python 3 or both.
|
||||||
'Programming Language :: Python :: 2',
|
'Programming Language :: Python :: 3 :: Only',
|
||||||
'Programming Language :: Python :: 2.7',
|
'Programming Language :: Python :: 3.7',
|
||||||
|
'Programming Language :: Python :: 3.8',
|
||||||
|
'Programming Language :: Python :: 3.9',
|
||||||
|
'Programming Language :: Python :: 3.10',
|
||||||
|
'Programming Language :: Python :: 3.11',
|
||||||
|
'Programming Language :: Python :: 3.12',
|
||||||
],
|
],
|
||||||
|
|
||||||
# What does your project relate to?
|
# What does your project relate to?
|
||||||
|
@ -111,7 +116,7 @@ setup(
|
||||||
# your project is installed. For an analysis of "install_requires" vs pip's
|
# your project is installed. For an analysis of "install_requires" vs pip's
|
||||||
# requirements files see:
|
# requirements files see:
|
||||||
# https://packaging.python.org/en/latest/requirements.html
|
# https://packaging.python.org/en/latest/requirements.html
|
||||||
install_requires=['requests[socks]>=2.27.0'],
|
install_requires=['requests[socks]>=2.27.0', 'packaging>=23.2'],
|
||||||
|
|
||||||
# List additional groups of dependencies here (e.g. development
|
# List additional groups of dependencies here (e.g. development
|
||||||
# dependencies). You can install these using the following syntax,
|
# dependencies). You can install these using the following syntax,
|
||||||
|
|
Loading…
Add table
Reference in a new issue