From 6a152760c57d996a1b97661f2b0046b21c3efb85 Mon Sep 17 00:00:00 2001 From: "Tobias Wellnitz, DH1TW" Date: Thu, 28 Dec 2023 21:02:41 +0100 Subject: [PATCH] added support for python 3.12 --- .github/workflows/test.yml | 129 ++++++++++++++++++---- README.md | 20 ++-- docs/source/changelog.rst | 12 +++ docs/source/conf.py | 13 +-- pyhamtools/callinfo.py | 60 ++++------- pyhamtools/dxcluster.py | 23 ++-- pyhamtools/locator.py | 30 +++--- pyhamtools/lookuplib.py | 137 +++++++++--------------- pyhamtools/qsl.py | 3 +- pyhamtools/version.py | 2 +- requirements-pytest.txt | 2 +- setup.py | 3 - test/test_callinfo.py | 12 +-- test/test_clublog.py | 10 +- test/test_dxcluster.py | 10 +- test/test_eqsl.py | 7 +- test/test_locator_sunrise_sunset.py | 19 ++-- test/test_lookuplib.py | 7 +- test/test_lookuplib_clublogapi.py | 4 +- test/test_lookuplib_clublogxml.py | 49 ++++----- test/test_lookuplib_gettersetter_api.py | 21 ++-- test/test_lookuplib_qrz.py | 10 +- test/test_lookuplib_redis.py | 10 +- test/test_lotw.py | 9 +- 24 files changed, 289 insertions(+), 313 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dccc779..84f3389 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,14 +4,59 @@ on: [push, pull_request] jobs: test_linux: + # Ubuntu 20.04 is still required for python 3.6; this doesn't work on Ubuntu 22.04 anymore runs-on: "ubuntu-20.04" - name: "Ubuntu latest - Python ${{ matrix.python-version }}" + name: "Ubuntu 20.04 - Python ${{ matrix.python-version }}" + strategy: + matrix: + python-version: ["3.6"] + redis-version: [6] + + steps: + - uses: "actions/checkout@v3" + - uses: "actions/setup-python@v4" + with: + python-version: "${{ matrix.python-version }}" + cache: "pip" + cache-dependency-path: | + **/setup.py + **/requirements*.txt + + - name: "Install dependencies" + run: | + set -xe + sudo apt-get install -y libxml2-dev libxslt-dev + python -VV + python -m pip install --upgrade pip setuptools + python -m pip install -e . + python -m pip install -r requirements-pytest.txt + + - name: Start Redis + uses: supercharge/redis-github-action@1.2.0 + with: + redis-version: ${{ matrix.redis-version }} + + - name: "Run tests for ${{ matrix.python-version }}" + env: + CLUBLOG_APIKEY: ${{ secrets.CLUBLOG_APIKEY }} + QRZ_USERNAME: ${{ secrets.QRZ_USERNAME }} + QRZ_PWD: ${{ secrets.QRZ_PWD }} + PYTHON_VERSION: ${{ matrix.python-version }} + # delay the execution randomly by a couple of seconds to reduce the amount + # of concurrent API calls on Clublog and QRZ.com when all CI jobs execute simultaneously + run: | + sleep $[ ( $RANDOM % 10 ) + 1 ]s + pytest ./test + + test_linux_ubuntu_22: + runs-on: "ubuntu-22.04" + name: "Ubuntu 22.04 - Python ${{ matrix.python-version }}" env: USING_COVERAGE: '3.11' strategy: matrix: - python-version: ["2.7", "3.5", "3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "pypy2.7", "pypy3.7", "pypy3.8", "pypy3.9"] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "pypy3.7", "pypy3.8", "pypy3.9", "pypy3.10"] redis-version: [6] steps: @@ -49,8 +94,13 @@ jobs: # of concurrent API calls on Clublog and QRZ.com when all CI jobs execute simultaneously run: | sleep $[ ( $RANDOM % 10 ) + 1 ]s - pytest --cov=./ - if [[ $PYTHON_VERSION == 3.11 ]]; then codecov; fi + if [[ $PYTHON_VERSION == 3.11 ]] + then + pytest --cov=test/ + codecov + else + pytest test/ + fi cd docs && make html # publish_package: @@ -64,18 +114,58 @@ jobs: # user: __token__ # password: ${{ secrets.PYPI_API_TOKEN }} + + test_macos: + # Ubuntu 20.04 is still required for python 3.6; this doesn't work on Ubuntu 22.04 anymore + runs-on: "macos-12" + name: "MacOS 12 - Python ${{ matrix.python-version }}" + strategy: + matrix: + python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "pypy3.8", "pypy3.9", "pypy3.10"] + redis-version: [6] + + steps: + - uses: "actions/checkout@v3" + - uses: "actions/setup-python@v4" + with: + python-version: "${{ matrix.python-version }}" + cache: "pip" + cache-dependency-path: | + **/setup.py + **/requirements*.txt + + - name: "Install dependencies" + run: | + set -xe + python -VV + python -m pip install --upgrade pip setuptools + python -m pip install -e . + python -m pip install -r requirements-pytest.txt + + - name: Start Redis + uses: shogo82148/actions-setup-redis@v1.31.1 + with: + redis-version: ${{ matrix.redis-version }} + + - name: "Run tests for ${{ matrix.python-version }}" + env: + CLUBLOG_APIKEY: ${{ secrets.CLUBLOG_APIKEY }} + QRZ_USERNAME: ${{ secrets.QRZ_USERNAME }} + QRZ_PWD: ${{ secrets.QRZ_PWD }} + PYTHON_VERSION: ${{ matrix.python-version }} + # delay the execution randomly by a couple of seconds to reduce the amount + # of concurrent API calls on Clublog and QRZ.com when all CI jobs execute simultaneously + run: | + sleep $[ ( $RANDOM % 10 ) + 1 ] + pytest ./test + test_windows: runs-on: "windows-latest" name: "Windows latest - Python ${{ matrix.python-version }}" strategy: matrix: - # lxml support for windows/python3.11 still missing (Dec 2022) - # https://github.com/lxml/lxml/pull/360 - # python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11"] - # python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"] - # disable python 3.6 on windows due to lxml / bs4 problems on Github Actions - python-version: ["3.7", "3.8", "3.9", "3.10"] + python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] steps: - uses: "actions/checkout@v3" @@ -94,24 +184,27 @@ jobs: python -m pip install -r requirements-pytest.txt python -m pip install -r requirements-docs.txt - name: Setup redis - # We have to download and install a non-official redis windows port - # since there is no official redis version for windows. - # 5.0 is good enough for our purposes. After installing the msi, - # redis will startup as a service. # There are no github-actions supporting redis on windows. # Github Actions Container services are also not available for windows. + # We have to download and install a non-official redis windows port + # since there is no official redis version for windows. + # Redis is then installed an run as a service run: | - C:\msys64\usr\bin\wget.exe https://github.com/tporadowski/redis/releases/download/v5.0.14.1/Redis-x64-5.0.14.1.msi - msiexec /quiet /i Redis-x64-5.0.14.1.msi + C:\msys64\usr\bin\wget.exe https://github.com/redis-windows/redis-windows/releases/download/7.0.14/Redis-7.0.14-Windows-x64-with-Service.tar.gz + C:\msys64\usr\bin\tar.exe -xvzf Redis-7.0.14-Windows-x64-with-Service.tar.gz + sc.exe create Redis binpath=D:\a\pyhamtools\pyhamtools\Redis-7.0.14-Windows-x64-with-Service\RedisService.exe start= auto + net start Redis + - name: "Run tests for ${{ matrix.python-version }}" env: CLUBLOG_APIKEY: ${{ secrets.CLUBLOG_APIKEY }} QRZ_USERNAME: ${{ secrets.QRZ_USERNAME }} QRZ_PWD: ${{ secrets.QRZ_PWD }} PYTHON_VERSION: ${{ matrix.python-version }} - # delay the execution randomly by 1-20sec to reduce the + # give redis service time to startup and + # delay the execution randomly by 5-20sec to reduce the # amount of concurrent API calls on Clublog and QRZ.com # when all CI jobs execute simultaneously run: | - start-sleep -Seconds (1..10 | get-random) + start-sleep -Seconds (5..20 | get-random) pytest \ No newline at end of file diff --git a/README.md b/README.md index 4dfc6bb..3ec3d27 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![codecov](https://codecov.io/gh/dh1tw/pyhamtools/branch/master/graph/badge.svg)](https://codecov.io/gh/dh1tw/pyhamtools) [![PyPI version](https://badge.fury.io/py/pyhamtools.svg)](https://badge.fury.io/py/pyhamtools) -Pyhamtools is a set of functions and classes for Amateur Radio purpose. +Pyhamtools is a set of functions and classes for Amateur Radio purposes. Currently, the core part is the Callsign Lookup which decodes any amateur radio callsign string and provides the corresponding information (Country, DXCC entity, CQ Zone...etc). This basic functionality is needed for Logbooks, @@ -31,22 +31,22 @@ This Library is used in production at the [DXHeat.com DX Cluster](https://dxheat ## Compatibility -Pyhamtools is compatible with Python 2.7 and Python >=3.6. -We check compatibility on OSX, Windows, and Linux with the following Python -versions: +Pyhamtools is compatible with Python >=3.6. +We check compatibility on OSX, Windows, and Linux with the following Python versions: -* Python 2.7 (will be deprecated in 2023) -* Python 3.5 (has been deprecated in 2022) -* Python 3.6 (will be deprecated in 2023) -* Python 3.7 +* Python 3.6 (will be deprecated in 2024) +* Python 3.7 (will be deprecated in 2024) * Python 3.8 * Python 3.9 * Python 3.10 * Python 3.11 -* [pypy2](https://pypy.org/) (Python 2) -* [pypy3.7](https://pypy.org/) +* Python 3.12 +* [pypy3.7](https://pypy.org/) (will be deprecated in 2024) * [pypy3.8](https://pypy.org/) * [pypy3.9](https://pypy.org/) +* [pypy3.10](https://pypy.org/) + +The support for Python 2.7 and 3.5 has been deprecated at the end of 2023. The last version which supports Python 2.7 and Python 3.5 is 0.8.7. ## Documentation diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index d1a6f81..253db87 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -1,6 +1,18 @@ Changelog --------- + +PyHamtools 0.9.0 +================ + +28. December 2023 + +* Deprecated support for Python 2.7 and Python 3.5 +* Added Support for Python 3.12 +* Replaced pytz with datetime.timezone +* Added Continous Integration Jobs for MacOS (now supported by Github Actions) + + PyHamtools 0.8.7 ================ diff --git a/docs/source/conf.py b/docs/source/conf.py index 00e63dc..6356823 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -12,19 +12,8 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys -import os - from pyhamtools.version import __version__, __release__ -sys.path.insert(0,"/Users/user/projects/pyhamtools/pyhamtools") - - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) - # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. @@ -52,7 +41,7 @@ master_doc = 'index' # General information about the project. project = u'pyhamtools' -copyright = u'2019, Tobias Wellnitz, DH1TW' +copyright = u'2024, Tobias Wellnitz, DH1TW' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/pyhamtools/callinfo.py b/pyhamtools/callinfo.py index bde9959..681eb25 100644 --- a/pyhamtools/callinfo.py +++ b/pyhamtools/callinfo.py @@ -1,22 +1,11 @@ import re import logging -from datetime import datetime -import sys - -import pytz +from datetime import datetime, timezone from pyhamtools.consts import LookupConventions as const from pyhamtools.callsign_exceptions import callsign_exceptions -UTC = pytz.UTC - -if sys.version_info < (2, 7, ): - class NullHandler(logging.Handler): - def emit(self, record): - pass - - class Callinfo(object): """ The purpose of this class is to return data (country, latitude, longitude, CQ Zone...etc) for an @@ -37,10 +26,7 @@ class Callinfo(object): self._logger = logger else: self._logger = logging.getLogger(__name__) - if sys.version_info[:2] == (2, 6): - self._logger.addHandler(NullHandler()) - else: - self._logger.addHandler(logging.NullHandler()) + self._logger.addHandler(logging.NullHandler()) self._lookuplib = lookuplib self._callsign_info = None @@ -81,10 +67,10 @@ class Callinfo(object): """truncate call until it corresponds to a Prefix in the database""" prefix = callsign if timestamp is None: - timestamp = datetime.utcnow().replace(tzinfo=UTC) + timestamp = datetime.now(timezone.utc) if re.search('(VK|AX|VI)9[A-Z]{3}', callsign): #special rule for VK9 calls - if timestamp > datetime(2006,1,1, tzinfo=UTC): + if timestamp > datetime(2006,1,1, tzinfo=timezone.utc): prefix = callsign[0:3]+callsign[4:5] while len(prefix) > 0: @@ -115,7 +101,7 @@ class Callinfo(object): Args: callsign (str): Amateur Radio callsign - timestamp (datetime, optional): datetime in UTC (tzinfo=pytz.UTC) + timestamp (datetime, optional): datetime in UTC (tzinfo=timezone.utc) Raises: KeyError: Callsign could not be identified @@ -124,7 +110,7 @@ class Callinfo(object): """ entire_callsign = callsign.upper() if timestamp is None: - timestamp = datetime.utcnow().replace(tzinfo=UTC) + timestamp = datetime.now(timezone.utc) if re.search('[/A-Z0-9\\-]{3,15}', entire_callsign): # make sure the call has at least 3 characters @@ -230,7 +216,7 @@ class Callinfo(object): def _lookup_callsign(self, callsign, timestamp=None): if timestamp is None: - timestamp = datetime.utcnow().replace(tzinfo=UTC) + timestamp = datetime.now(timezone.utc) # Check if operation is invalid invalid = False @@ -278,7 +264,7 @@ class Callinfo(object): Args: callsign (str): Amateur Radio callsign - timestamp (datetime, optional): datetime in UTC (tzinfo=pytz.UTC) + timestamp (datetime, optional): datetime in UTC (tzinfo=timezone.utc) Returns: dict: Dictionary containing the callsign specific data @@ -315,7 +301,7 @@ class Callinfo(object): callsign = callsign.upper() if timestamp is None: - timestamp = datetime.utcnow().replace(tzinfo=UTC) + timestamp = datetime.now(timezone.utc) callsign_data = self._lookup_callsign(callsign, timestamp) @@ -332,7 +318,7 @@ class Callinfo(object): Args: callsign (str): Amateur Radio callsign - timestamp (datetime, optional): datetime in UTC (tzinfo=pytz.UTC) + timestamp (datetime, optional): datetime in UTC (tzinfo=timezone.utc) Returns: bool: True / False @@ -348,7 +334,7 @@ class Callinfo(object): """ if timestamp is None: - timestamp = datetime.utcnow().replace(tzinfo=UTC) + timestamp = datetime.now(timezone.utc) try: if self.get_all(callsign, timestamp): @@ -361,7 +347,7 @@ class Callinfo(object): Args: callsign (str): Amateur Radio callsign - timestamp (datetime, optional): datetime in UTC (tzinfo=pytz.UTC) + timestamp (datetime, optional): datetime in UTC (tzinfo=timezone.utc) Returns: dict: Containing Latitude and Longitude @@ -388,7 +374,7 @@ class Callinfo(object): """ if timestamp is None: - timestamp = datetime.utcnow().replace(tzinfo=UTC) + timestamp = datetime.now(timezone.utc) callsign_data = self.get_all(callsign, timestamp=timestamp) return { @@ -401,7 +387,7 @@ class Callinfo(object): Args: callsign (str): Amateur Radio callsign - timestamp (datetime, optional): datetime in UTC (tzinfo=pytz.UTC) + timestamp (datetime, optional): datetime in UTC (tzinfo=timezone.utc) Returns: int: containing the callsign's CQ Zone @@ -411,7 +397,7 @@ class Callinfo(object): """ if timestamp is None: - timestamp = datetime.utcnow().replace(tzinfo=UTC) + timestamp = datetime.now(timezone.utc) return self.get_all(callsign, timestamp)[const.CQZ] @@ -420,7 +406,7 @@ class Callinfo(object): Args: callsign (str): Amateur Radio callsign - timestamp (datetime, optional): datetime in UTC (tzinfo=pytz.UTC) + timestamp (datetime, optional): datetime in UTC (tzinfo=timezone.utc) Returns: int: containing the callsign's CQ Zone @@ -433,7 +419,7 @@ class Callinfo(object): """ if timestamp is None: - timestamp = datetime.utcnow().replace(tzinfo=UTC) + timestamp = datetime.now(timezone.utc) return self.get_all(callsign, timestamp)[const.ITUZ] @@ -442,7 +428,7 @@ class Callinfo(object): Args: callsign (str): Amateur Radio callsign - timestamp (datetime, optional): datetime in UTC (tzinfo=pytz.UTC) + timestamp (datetime, optional): datetime in UTC (tzinfo=timezone.utc) Returns: str: name of the Country @@ -460,7 +446,7 @@ class Callinfo(object): """ if timestamp is None: - timestamp = datetime.utcnow().replace(tzinfo=UTC) + timestamp = datetime.now(timezone.utc) return self.get_all(callsign, timestamp)[const.COUNTRY] @@ -469,7 +455,7 @@ class Callinfo(object): Args: callsign (str): Amateur Radio callsign - timestamp (datetime, optional): datetime in UTC (tzinfo=pytz.UTC) + timestamp (datetime, optional): datetime in UTC (tzinfo=timezone.utc) Returns: int: containing the country ADIF id @@ -479,7 +465,7 @@ class Callinfo(object): """ if timestamp is None: - timestamp = datetime.utcnow().replace(tzinfo=UTC) + timestamp = datetime.now(timezone.utc) return self.get_all(callsign, timestamp)[const.ADIF] @@ -488,7 +474,7 @@ class Callinfo(object): Args: callsign (str): Amateur Radio callsign - timestamp (datetime, optional): datetime in UTC (tzinfo=pytz.UTC) + timestamp (datetime, optional): datetime in UTC (tzinfo=timezone.utc) Returns: str: continent identified @@ -508,6 +494,6 @@ class Callinfo(object): - AN: Antarctica """ if timestamp is None: - timestamp = datetime.utcnow().replace(tzinfo=UTC) + timestamp = datetime.now(timezone.utc) return self.get_all(callsign, timestamp)[const.CONTINENT] diff --git a/pyhamtools/dxcluster.py b/pyhamtools/dxcluster.py index 2417382..f427a76 100644 --- a/pyhamtools/dxcluster.py +++ b/pyhamtools/dxcluster.py @@ -1,37 +1,30 @@ __author__ = 'dh1tw' -from datetime import datetime +from datetime import datetime, timezone from time import strptime, mktime import re -import pytz - from pyhamtools.consts import LookupConventions as const - -UTC = pytz.UTC - - - def decode_char_spot(raw_string): """Chop Line from DX-Cluster into pieces and return a dict with the spot data""" data = {} # Spotter callsign - if re.match('[A-Za-z0-9\/]+[:$]', raw_string[6:15]): - data[const.SPOTTER] = re.sub(':', '', re.match('[A-Za-z0-9\/]+[:$]', raw_string[6:15]).group(0)) + if re.match(r'[A-Za-z0-9\/]+[:$]', raw_string[6:15]): + data[const.SPOTTER] = re.sub(':', '', re.match(r'[A-Za-z0-9\/]+[:$]', raw_string[6:15]).group(0)) else: raise ValueError - if re.search('[0-9\.]{5,12}', raw_string[10:25]): - data[const.FREQUENCY] = float(re.search('[0-9\.]{5,12}', raw_string[10:25]).group(0)) + if re.search(r'[0-9\.]{5,12}', raw_string[10:25]): + data[const.FREQUENCY] = float(re.search(r'[0-9\.]{5,12}', raw_string[10:25]).group(0)) else: raise ValueError - data[const.DX] = re.sub('[^A-Za-z0-9\/]+', '', raw_string[26:38]) - data[const.COMMENT] = re.sub('[^\sA-Za-z0-9\.,;\#\+\-!\?\$\(\)@\/]+', ' ', raw_string[39:69]).strip() - data[const.TIME] = datetime.now().replace(tzinfo=UTC) + data[const.DX] = re.sub(r'[^A-Za-z0-9\/]+', '', raw_string[26:38]) + data[const.COMMENT] = re.sub(r'[^\sA-Za-z0-9\.,;\#\+\-!\?\$\(\)@\/]+', ' ', raw_string[39:69]).strip() + data[const.TIME] = datetime.now(timezone.utc) return data diff --git a/pyhamtools/locator.py b/pyhamtools/locator.py index 0660892..2f0872a 100644 --- a/pyhamtools/locator.py +++ b/pyhamtools/locator.py @@ -1,12 +1,8 @@ -from __future__ import division from math import pi, sin, cos, atan2, sqrt, radians, log, tan, degrees -from datetime import datetime +from datetime import datetime, timezone -import pytz import ephem -UTC = pytz.UTC - def latlong_to_locator (latitude, longitude): """converts WGS84 coordinates into the corresponding Maidenhead Locator @@ -283,16 +279,14 @@ def calculate_sunrise_sunset(locator, calc_date=None): The following calculates the next sunrise & sunset for JN48QM on the 1./Jan/2014 >>> from pyhamtools.locator import calculate_sunrise_sunset - >>> from datetime import datetime - >>> import pytz - >>> UTC = pytz.UTC - >>> myDate = datetime(year=2014, month=1, day=1, tzinfo=UTC) + >>> from datetime import datetime, timezone + >>> myDate = datetime(year=2014, month=1, day=1, tzinfo=timezone.utc) >>> calculate_sunrise_sunset("JN48QM", myDate) { - 'morning_dawn': datetime.datetime(2014, 1, 1, 6, 36, 51, 710524, tzinfo=), - 'sunset': datetime.datetime(2014, 1, 1, 16, 15, 23, 31016, tzinfo=), - 'evening_dawn': datetime.datetime(2014, 1, 1, 15, 38, 8, 355315, tzinfo=), - 'sunrise': datetime.datetime(2014, 1, 1, 7, 14, 6, 162063, tzinfo=) + 'morning_dawn': datetime.datetime(2014, 1, 1, 6, 36, 51, 710524, tzinfo=datetime.timezone.utc), + 'sunset': datetime.datetime(2014, 1, 1, 16, 15, 23, 31016, tzinfo=datetime.timezone.utc), + 'evening_dawn': datetime.datetime(2014, 1, 1, 15, 38, 8, 355315, tzinfo=datetime.timezone.utc), + 'sunrise': datetime.datetime(2014, 1, 1, 7, 14, 6, 162063, tzinfo=datetime.timezone.utc) } """ @@ -304,7 +298,7 @@ def calculate_sunrise_sunset(locator, calc_date=None): latitude, longitude = locator_to_latlong(locator) if calc_date is None: - calc_date = datetime.utcnow() + calc_date = datetime.now(timezone.utc) if type(calc_date) != datetime: raise ValueError @@ -350,11 +344,11 @@ def calculate_sunrise_sunset(locator, calc_date=None): result['sunset'] = sunset if morning_dawn: - result['morning_dawn'] = morning_dawn.replace(tzinfo=UTC) + result['morning_dawn'] = morning_dawn.replace(tzinfo=timezone.utc) if sunrise: - result['sunrise'] = sunrise.replace(tzinfo=UTC) + result['sunrise'] = sunrise.replace(tzinfo=timezone.utc) if evening_dawn: - result['evening_dawn'] = evening_dawn.replace(tzinfo=UTC) + result['evening_dawn'] = evening_dawn.replace(tzinfo=timezone.utc) if sunset: - result['sunset'] = sunset.replace(tzinfo=UTC) + result['sunset'] = sunset.replace(tzinfo=timezone.utc) return result diff --git a/pyhamtools/lookuplib.py b/pyhamtools/lookuplib.py index 28e0b18..23bd6ef 100644 --- a/pyhamtools/lookuplib.py +++ b/pyhamtools/lookuplib.py @@ -1,38 +1,24 @@ -from __future__ import unicode_literals import os import logging import logging.config import re import random, string -from datetime import datetime +from datetime import datetime, timezone import xml.etree.ElementTree as ET import urllib import json import copy -import sys -import unicodedata import requests from requests.exceptions import ConnectionError, HTTPError, Timeout from bs4 import BeautifulSoup -import pytz from . import version from .consts import LookupConventions as const from .exceptions import APIKeyMissingError -UTC = pytz.UTC - REDIS_LUA_DEL_SCRIPT = "local keys = redis.call('keys', ARGV[1]) \n for i=1,#keys,20000 do \n redis.call('del', unpack(keys, i, math.min(i+19999, #keys))) \n end \n return keys" -if sys.version_info < (2, 7,): - class NullHandler(logging.Handler): - def emit(self, record): - pass - -if sys.version_info.major == 3: - unicode = str - class LookupLib(object): """ @@ -80,10 +66,7 @@ class LookupLib(object): self._logger = logger else: self._logger = logging.getLogger(__name__) - if sys.version_info[:2] == (2, 6): - self._logger.addHandler(NullHandler()) - else: - self._logger.addHandler(logging.NullHandler()) + self._logger.addHandler(logging.NullHandler()) self._apikey = apikey self._apiv = apiv @@ -131,10 +114,7 @@ class LookupLib(object): "agent" : agent } - if sys.version_info.major == 3: - encodeurl = url + "?" + urllib.parse.urlencode(params) - else: - encodeurl = url + "?" + urllib.urlencode(params) + encodeurl = url + "?" + urllib.parse.urlencode(params) response = requests.get(encodeurl, timeout=10) doc = BeautifulSoup(response.text, "xml") session_key = None @@ -334,7 +314,7 @@ class LookupLib(object): Args: callsign (string): Amateur radio callsign - timestamp (datetime, optional): datetime in UTC (tzinfo=pytz.UTC) + timestamp (datetime, optional): datetime in UTC (tzinfo=timezone.utc) Returns: dict: Dictionary containing the country specific data of the callsign @@ -347,10 +327,9 @@ class LookupLib(object): The following code queries the the online Clublog API for the callsign "VK9XO" on a specific date. >>> from pyhamtools import LookupLib - >>> from datetime import datetime - >>> import pytz + >>> from datetime import datetime, timezone >>> my_lookuplib = LookupLib(lookuptype="clublogapi", apikey="myapikey") - >>> timestamp = datetime(year=1962, month=7, day=7, tzinfo=pytz.UTC) + >>> timestamp = datetime(year=1962, month=7, day=7, tzinfo=timezone.utc) >>> print my_lookuplib.lookup_callsign("VK9XO", timestamp) { 'country': u'CHRISTMAS ISLAND', @@ -374,7 +353,7 @@ class LookupLib(object): """ callsign = callsign.strip().upper() if timestamp is None: - timestamp = datetime.utcnow().replace(tzinfo=UTC) + timestamp = datetime.now(timezone.utc) if self._lookuptype == "clublogapi": callsign_data = self._lookup_clublogAPI(callsign=callsign, timestamp=timestamp, apikey=self._apikey) @@ -499,7 +478,7 @@ class LookupLib(object): Args: prefix (string): Prefix of a Amateur Radio callsign - timestamp (datetime, optional): datetime in UTC (tzinfo=pytz.UTC) + timestamp (datetime, optional): datetime in UTC (tzinfo=timezone.utc) Returns: dict: Dictionary containing the country specific data of the Prefix @@ -536,7 +515,7 @@ class LookupLib(object): prefix = prefix.strip().upper() if timestamp is None: - timestamp = datetime.utcnow().replace(tzinfo=UTC) + timestamp = datetime.now(timezone.utc) if self._lookuptype == "clublogxml" or self._lookuptype == "countryfile": @@ -556,7 +535,7 @@ class LookupLib(object): Args: callsign (string): Amateur Radio callsign - timestamp (datetime, optional): datetime in UTC (tzinfo=pytz.UTC) + timestamp (datetime, optional): datetime in UTC (tzinfo=timezone.utc) Returns: bool: True if a record exists for this callsign (at the given time) @@ -569,13 +548,12 @@ class LookupLib(object): The following code checks the Clublog XML database if the operation is valid for two dates. >>> from pyhamtools import LookupLib - >>> from datetime import datetime - >>> import pytz + >>> from datetime import datetime, timezone >>> my_lookuplib = LookupLib(lookuptype="clublogxml", apikey="myapikey") >>> print my_lookuplib.is_invalid_operation("5W1CFN") True >>> try: - >>> timestamp = datetime(year=2012, month=1, day=31).replace(tzinfo=pytz.UTC) + >>> timestamp = datetime(year=2012, month=1, day=31, tzinfo=timezone.utc) >>> my_lookuplib.is_invalid_operation("5W1CFN", timestamp) >>> except KeyError: >>> print "Seems to be invalid operation before 31.1.2012" @@ -591,7 +569,7 @@ class LookupLib(object): callsign = callsign.strip().upper() if timestamp is None: - timestamp = datetime.utcnow().replace(tzinfo=UTC) + timestamp = datetime.now(timezone.utc) if self._lookuptype == "clublogxml": @@ -645,7 +623,7 @@ class LookupLib(object): Args: callsign (string): Amateur radio callsign - timestamp (datetime, optional): datetime in UTC (tzinfo=pytz.UTC) + timestamp (datetime, optional): datetime in UTC (tzinfo=timezone.utc) Returns: int: Value of the the CQ Zone exception which exists for this callsign (at the given time) @@ -675,7 +653,7 @@ class LookupLib(object): callsign = callsign.strip().upper() if timestamp is None: - timestamp = datetime.utcnow().replace(tzinfo=UTC) + timestamp = datetime.now(timezone.utc) if self._lookuptype == "clublogxml": @@ -704,12 +682,9 @@ class LookupLib(object): } if timestamp is None: - timestamp = datetime.utcnow().replace(tzinfo=UTC) + timestamp = datetime.now(timezone.utc) - if sys.version_info.major == 3: - encodeurl = url + "?" + urllib.parse.urlencode(params) - else: - encodeurl = url + "?" + urllib.urlencode(params) + encodeurl = url + "?" + urllib.parse.urlencode(params) response = requests.get(encodeurl, timeout=5) if not self._check_html_response(response): @@ -740,10 +715,7 @@ class LookupLib(object): "callsign" : callsign, } - if sys.version_info.major == 3: - encodeurl = url + "?" + urllib.parse.urlencode(params) - else: - encodeurl = url + "?" + urllib.urlencode(params) + encodeurl = url + "?" + urllib.parse.urlencode(params) response = requests.get(encodeurl, timeout=5) return response @@ -756,10 +728,7 @@ class LookupLib(object): "dxcc" : str(dxcc_or_callsign), } - if sys.version_info.major == 3: - encodeurl = url + "?" + urllib.parse.urlencode(params) - else: - encodeurl = url + "?" + urllib.urlencode(params) + encodeurl = url + "?" + urllib.parse.urlencode(params) response = requests.get(encodeurl, timeout=5) return response @@ -890,12 +859,12 @@ class LookupLib(object): lookup[const.LAND] = root.Callsign.land.text if root.Callsign.efdate: try: - lookup[const.EFDATE] = datetime.strptime(root.Callsign.efdate.text, '%Y-%m-%d').replace(tzinfo=UTC) + lookup[const.EFDATE] = datetime.strptime(root.Callsign.efdate.text, '%Y-%m-%d').replace(tzinfo=timezone.utc) except ValueError: self._logger.debug("[QRZ.com] efdate: Invalid DateTime; " + callsign + " " + root.Callsign.efdate.text) if root.Callsign.expdate: try: - lookup[const.EXPDATE] = datetime.strptime(root.Callsign.expdate.text, '%Y-%m-%d').replace(tzinfo=UTC) + lookup[const.EXPDATE] = datetime.strptime(root.Callsign.expdate.text, '%Y-%m-%d').replace(tzinfo=timezone.utc) except ValueError: self._logger.debug("[QRZ.com] expdate: Invalid DateTime; " + callsign + " " + root.Callsign.expdate.text) if root.Callsign.p_call: @@ -916,7 +885,7 @@ class LookupLib(object): lookup[const.BIO] = root.Callsign.bio.text if root.Callsign.biodate: try: - lookup[const.BIODATE] = datetime.strptime(root.Callsign.biodate.text, '%Y-%m-%d %H:%M:%S').replace(tzinfo=UTC) + lookup[const.BIODATE] = datetime.strptime(root.Callsign.biodate.text, '%Y-%m-%d %H:%M:%S').replace(tzinfo=timezone.utc) except ValueError: self._logger.warning("[QRZ.com] biodate: Invalid DateTime; " + callsign) if root.Callsign.image: @@ -927,7 +896,7 @@ class LookupLib(object): lookup[const.SERIAL] = long(root.Callsign.serial.text) if root.Callsign.moddate: try: - lookup[const.MODDATE] = datetime.strptime(root.Callsign.moddate.text, '%Y-%m-%d %H:%M:%S').replace(tzinfo=UTC) + lookup[const.MODDATE] = datetime.strptime(root.Callsign.moddate.text, '%Y-%m-%d %H:%M:%S').replace(tzinfo=timezone.utc) except ValueError: self._logger.warning("[QRZ.com] moddate: Invalid DateTime; " + callsign) if root.Callsign.MSA: @@ -971,10 +940,6 @@ class LookupLib(object): if root.Callsign.geoloc: lookup[const.GEOLOC] = root.Callsign.geoloc.text - # if sys.version_info >= (2,): - # for item in lookup: - # if isinstance(lookup[item], unicode): - # print item, repr(lookup[item]) return lookup def _load_clublogXML(self, @@ -1135,7 +1100,7 @@ class LookupLib(object): if cty_date: cty_date = cty_date.group(0).replace("date=", "").replace("'", "") cty_date = datetime.strptime(cty_date[:19], '%Y-%m-%dT%H:%M:%S') - cty_date.replace(tzinfo=UTC) + cty_date.replace(tzinfo=timezone.utc) cty_header["Date"] = cty_date cty_ns = re.search("xmlns='.+[']", raw_header) @@ -1215,10 +1180,10 @@ class LookupLib(object): entity = {} for item in cty_entity: if item.tag == "name": - entity[const.COUNTRY] = unicode(item.text) - self._logger.debug(unicode(item.text)) + entity[const.COUNTRY] = str(item.text) + self._logger.debug(str(item.text)) elif item.tag == "prefix": - entity[const.PREFIX] = unicode(item.text) + entity[const.PREFIX] = str(item.text) elif item.tag == "deleted": if item.text == "TRUE": entity[const.DELETED] = True @@ -1227,17 +1192,17 @@ class LookupLib(object): elif item.tag == "cqz": entity[const.CQZ] = int(item.text) elif item.tag == "cont": - entity[const.CONTINENT] = unicode(item.text) + entity[const.CONTINENT] = str(item.text) elif item.tag == "long": entity[const.LONGITUDE] = float(item.text) elif item.tag == "lat": entity[const.LATITUDE] = float(item.text) elif item.tag == "start": dt = datetime.strptime(item.text[:19], '%Y-%m-%dT%H:%M:%S') - entity[const.START] = dt.replace(tzinfo=UTC) + entity[const.START] = dt.replace(tzinfo=timezone.utc) elif item.tag == "end": dt = datetime.strptime(item.text[:19], '%Y-%m-%dT%H:%M:%S') - entity[const.END] = dt.replace(tzinfo=UTC) + entity[const.END] = dt.replace(tzinfo=timezone.utc) elif item.tag == "whitelist": if item.text == "TRUE": entity[const.WHITELIST] = True @@ -1245,10 +1210,10 @@ class LookupLib(object): entity[const.WHITELIST] = False elif item.tag == "whitelist_start": dt = datetime.strptime(item.text[:19], '%Y-%m-%dT%H:%M:%S') - entity[const.WHITELIST_START] = dt.replace(tzinfo=UTC) + entity[const.WHITELIST_START] = dt.replace(tzinfo=timezone.utc) elif item.tag == "whitelist_end": dt = datetime.strptime(item.text[:19], '%Y-%m-%dT%H:%M:%S') - entity[const.WHITELIST_END] = dt.replace(tzinfo=UTC) + entity[const.WHITELIST_END] = dt.replace(tzinfo=timezone.utc) except AttributeError: self._logger.error("Error while processing: ") entities[int(cty_entity[0].text)] = entity @@ -1269,23 +1234,23 @@ class LookupLib(object): else: call_exceptions_index[call] = [int(cty_exception.attrib["record"])] elif item.tag == "entity": - call_exception[const.COUNTRY] = unicode(item.text) + call_exception[const.COUNTRY] = str(item.text) elif item.tag == "adif": call_exception[const.ADIF] = int(item.text) elif item.tag == "cqz": call_exception[const.CQZ] = int(item.text) elif item.tag == "cont": - call_exception[const.CONTINENT] = unicode(item.text) + call_exception[const.CONTINENT] = str(item.text) elif item.tag == "long": call_exception[const.LONGITUDE] = float(item.text) elif item.tag == "lat": call_exception[const.LATITUDE] = float(item.text) elif item.tag == "start": dt = datetime.strptime(item.text[:19], '%Y-%m-%dT%H:%M:%S') - call_exception[const.START] = dt.replace(tzinfo=UTC) + call_exception[const.START] = dt.replace(tzinfo=timezone.utc) elif item.tag == "end": dt = datetime.strptime(item.text[:19], '%Y-%m-%dT%H:%M:%S') - call_exception[const.END] = dt.replace(tzinfo=UTC) + call_exception[const.END] = dt.replace(tzinfo=timezone.utc) call_exceptions[int(cty_exception.attrib["record"])] = call_exception self._logger.debug(str(len(call_exceptions))+" Exceptions added") @@ -1310,23 +1275,23 @@ class LookupLib(object): else: prefixes_index[call] = [int(cty_prefix.attrib["record"])] if item.tag == "entity": - prefix[const.COUNTRY] = unicode(item.text) + prefix[const.COUNTRY] = str(item.text) elif item.tag == "adif": prefix[const.ADIF] = int(item.text) elif item.tag == "cqz": prefix[const.CQZ] = int(item.text) elif item.tag == "cont": - prefix[const.CONTINENT] = unicode(item.text) + prefix[const.CONTINENT] = str(item.text) elif item.tag == "long": prefix[const.LONGITUDE] = float(item.text) elif item.tag == "lat": prefix[const.LATITUDE] = float(item.text) elif item.tag == "start": dt = datetime.strptime(item.text[:19], '%Y-%m-%dT%H:%M:%S') - prefix[const.START] = dt.replace(tzinfo=UTC) + prefix[const.START] = dt.replace(tzinfo=timezone.utc) elif item.tag == "end": dt = datetime.strptime(item.text[:19], '%Y-%m-%dT%H:%M:%S') - prefix[const.END] = dt.replace(tzinfo=UTC) + prefix[const.END] = dt.replace(tzinfo=timezone.utc) prefixes[int(cty_prefix.attrib["record"])] = prefix self._logger.debug(str(len(prefixes))+" Prefixes added") @@ -1349,10 +1314,10 @@ class LookupLib(object): elif item.tag == "start": dt = datetime.strptime(item.text[:19], '%Y-%m-%dT%H:%M:%S') - invalid_operation[const.START] = dt.replace(tzinfo=UTC) + invalid_operation[const.START] = dt.replace(tzinfo=timezone.utc) elif item.tag == "end": dt = datetime.strptime(item.text[:19], '%Y-%m-%dT%H:%M:%S') - invalid_operation[const.END] = dt.replace(tzinfo=UTC) + invalid_operation[const.END] = dt.replace(tzinfo=timezone.utc) invalid_operations[int(cty_inv_operation.attrib["record"])] = invalid_operation self._logger.debug(str(len(invalid_operations))+" Invalid Operations added") @@ -1378,10 +1343,10 @@ class LookupLib(object): zoneException[const.CQZ] = int(item.text) elif item.tag == "start": dt = datetime.strptime(item.text[:19], '%Y-%m-%dT%H:%M:%S') - zoneException[const.START] = dt.replace(tzinfo=UTC) + zoneException[const.START] = dt.replace(tzinfo=timezone.utc) elif item.tag == "end": dt = datetime.strptime(item.text[:19], '%Y-%m-%dT%H:%M:%S') - zoneException[const.END] = dt.replace(tzinfo=UTC) + zoneException[const.END] = dt.replace(tzinfo=timezone.utc) zone_exceptions[int(cty_zone_exception.attrib["record"])] = zoneException self._logger.debug(str(len(zone_exceptions))+" Zone Exceptions added") @@ -1437,12 +1402,12 @@ class LookupLib(object): for item in cty_list: entry = {} call = str(item) - entry[const.COUNTRY] = unicode(cty_list[item]["Country"]) + entry[const.COUNTRY] = str(cty_list[item]["Country"]) if mapping: entry[const.ADIF] = int(mapping[cty_list[item]["Country"]]) entry[const.CQZ] = int(cty_list[item]["CQZone"]) entry[const.ITUZ] = int(cty_list[item]["ITUZone"]) - entry[const.CONTINENT] = unicode(cty_list[item]["Continent"]) + entry[const.CONTINENT] = str(cty_list[item]["Continent"]) entry[const.LATITUDE] = float(cty_list[item]["Latitude"]) entry[const.LONGITUDE] = float(cty_list[item]["Longitude"])*(-1) @@ -1534,17 +1499,17 @@ class LookupLib(object): elif item == const.LONGITUDE: my_dict[item] = float(my_dict[item]) elif item == const.START: - my_dict[item] = datetime.strptime(my_dict[item], '%Y-%m-%d%H:%M:%S').replace(tzinfo=UTC) + my_dict[item] = datetime.strptime(my_dict[item], '%Y-%m-%d%H:%M:%S').replace(tzinfo=timezone.utc) elif item == const.END: - my_dict[item] = datetime.strptime(my_dict[item], '%Y-%m-%d%H:%M:%S').replace(tzinfo=UTC) + my_dict[item] = datetime.strptime(my_dict[item], '%Y-%m-%d%H:%M:%S').replace(tzinfo=timezone.utc) elif item == const.WHITELIST_START: - my_dict[item] = datetime.strptime(my_dict[item], '%Y-%m-%d%H:%M:%S').replace(tzinfo=UTC) + my_dict[item] = datetime.strptime(my_dict[item], '%Y-%m-%d%H:%M:%S').replace(tzinfo=timezone.utc) elif item == const.WHITELIST_END: - my_dict[item] = datetime.strptime(my_dict[item], '%Y-%m-%d%H:%M:%S').replace(tzinfo=UTC) + my_dict[item] = datetime.strptime(my_dict[item], '%Y-%m-%d%H:%M:%S').replace(tzinfo=timezone.utc) elif item == const.WHITELIST: my_dict[item] = self._str_to_bool(my_dict[item]) else: - my_dict[item] = unicode(my_dict[item]) + my_dict[item] = str(my_dict[item]) return my_dict diff --git a/pyhamtools/qsl.py b/pyhamtools/qsl.py index da1e891..ca41fb0 100644 --- a/pyhamtools/qsl.py +++ b/pyhamtools/qsl.py @@ -1,4 +1,3 @@ -from future.utils import iteritems from datetime import datetime import re @@ -125,7 +124,7 @@ def get_clublog_users(**kwargs): error_count = 0 - for call, call_data in iteritems(cl_data): + for call, call_data in cl_data.items(): try: data = {} if "firstqso" in call_data: diff --git a/pyhamtools/version.py b/pyhamtools/version.py index 3cbc6a7..04ad96d 100644 --- a/pyhamtools/version.py +++ b/pyhamtools/version.py @@ -1,3 +1,3 @@ -VERSION = (0, 8, 7) +VERSION = (0, 9, 0) __release__ = ''.join(['-.'[type(x) == int]+str(x) for x in VERSION])[1:] __version__ = '.'.join((str(VERSION[0]), str(VERSION[1]))) diff --git a/requirements-pytest.txt b/requirements-pytest.txt index 5f3f75e..c086f37 100644 --- a/requirements-pytest.txt +++ b/requirements-pytest.txt @@ -1,5 +1,5 @@ pytest>=7.0.0; python_version>='3.7' -pytest==4.6.11; python_version<='3.6' and python_version>='2.7' +pytest==4.6.11; python_version=='3.6' pytest-blockage>=0.2.2 pytest-localserver>=0.5 pytest-cov>=2.12 diff --git a/setup.py b/setup.py index a4194df..63e6053 100755 --- a/setup.py +++ b/setup.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -import sys import os from distutils.core import setup @@ -16,12 +15,10 @@ setup(name='pyhamtools', package_data={'': ['countryfilemapping.json']}, packages=['pyhamtools'], install_requires=[ - "pytz>=2019.1", "requests>=2.21.0", "ephem>=4.1.3", "beautifulsoup4>=4.7.1", "lxml>=4.8.0", - "future>=0.18.2", "redis>=2.10.6", ], **kw diff --git a/test/test_callinfo.py b/test/test_callinfo.py index ab4f37a..2d20541 100644 --- a/test/test_callinfo.py +++ b/test/test_callinfo.py @@ -1,14 +1,10 @@ # -*- coding: utf-8 -*- -from datetime import datetime +from datetime import datetime, timezone import pytest -import pytz from pyhamtools.consts import LookupConventions as const -UTC = pytz.UTC - - response_prefix_DH_clublog = { 'country': 'FEDERAL REPUBLIC OF GERMANY', 'adif': 230, @@ -198,8 +194,8 @@ response_Exception_VK9XO_with_start_date = { 'adif': 35, 'country': 'CHRISTMAS ISLAND', 'continent': 'OC', - 'latitude': -10.50, - 'longitude': 105.70, + 'latitude': -10.48, + 'longitude': 105.71, 'cqz': 29 } @@ -389,7 +385,7 @@ class Test_callinfo_methods: if fix_callinfo._lookuplib._lookuptype == "clublogxml" or fix_callinfo._lookuplib._lookuptype == "clublogapi": assert fix_callinfo.get_all("DH1TW") == response_prefix_DH_clublog assert fix_callinfo.get_all("ci8aw") == response_zone_exception_ci8aw - timestamp = datetime(year=2016, month=1, day=20, tzinfo=UTC) + timestamp = datetime(year=2016, month=1, day=20, tzinfo=timezone.utc) assert fix_callinfo.get_all("VP8STI", timestamp) == response_Exception_VP8STI_with_start_and_stop_date elif fix_callinfo._lookuplib._lookuptype == "countryfile": diff --git a/test/test_clublog.py b/test/test_clublog.py index c9bf6b9..8b3cae7 100644 --- a/test/test_clublog.py +++ b/test/test_clublog.py @@ -1,15 +1,9 @@ import os -import sys -import datetime import pytest -from future.utils import iteritems from pyhamtools.qsl import get_clublog_users -if sys.version_info.major == 3: - unicode = str - test_dir = os.path.dirname(os.path.abspath(__file__)) fix_dir = os.path.join(test_dir, 'fixtures') @@ -26,8 +20,8 @@ class Test_clublog_methods: data = get_clublog_users() assert isinstance(data, dict) - for key, value in iteritems(data): - assert isinstance(key, unicode) + for key, value in data.items(): + assert isinstance(key, str) assert isinstance(value, dict) def test_with_invalid_url(self): diff --git a/test/test_dxcluster.py b/test/test_dxcluster.py index 4092978..afb6dcb 100644 --- a/test/test_dxcluster.py +++ b/test/test_dxcluster.py @@ -1,15 +1,9 @@ import pytest -from datetime import datetime - - -import pytz - +from datetime import datetime, timezone from pyhamtools.consts import LookupConventions as const from pyhamtools.dxcluster import decode_char_spot, decode_pc11_message, decode_pc61_message -UTC = pytz.UTC - fix_spot1 = "DX de CT3FW: 21004.8 HC2AO 599 TKS(CW)QSL READ,QRZ.COM 2132Z" fix_spot1_broken_spotter_call = "DX de $QRM: 21004.8 HC2AO 599 TKS(CW)QSL READ,QRZ.COM 2132Z" @@ -34,7 +28,7 @@ response_spot1 = { const.BAND: 15, const.MODE: "CW", const.COMMENT: "599 TKS(CW)QSL READ,QRZ.COM", - const.TIME: datetime.utcnow().replace( hour=21, minute=32, second=0, microsecond = 0, tzinfo=UTC) + const.TIME: datetime.now(timezone.utc).replace(hour=21, minute=32, second=0, microsecond = 0) } diff --git a/test/test_eqsl.py b/test/test_eqsl.py index 9d0070e..35cca35 100644 --- a/test/test_eqsl.py +++ b/test/test_eqsl.py @@ -1,15 +1,10 @@ from .execfile import execfile import os -import sys -import datetime import pytest from pyhamtools.qsl import get_eqsl_users -if sys.version_info.major == 3: - unicode = str - test_dir = os.path.dirname(os.path.abspath(__file__)) fix_dir = os.path.join(test_dir, 'fixtures') class Test_eqsl_methods: @@ -26,7 +21,7 @@ class Test_eqsl_methods: data = get_eqsl_users() assert isinstance(data, list) for el in data: - assert isinstance(el, unicode) + assert isinstance(el, str) assert len(data) > 1000 def test_with_invalid_url(self): diff --git a/test/test_locator_sunrise_sunset.py b/test/test_locator_sunrise_sunset.py index a11b6b8..caf73ad 100644 --- a/test/test_locator_sunrise_sunset.py +++ b/test/test_locator_sunrise_sunset.py @@ -1,12 +1,9 @@ -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone import pytest -import pytz from pyhamtools.locator import calculate_sunrise_sunset -UTC = pytz.UTC - class Test_calculate_sunrise_sunset_normal_case(): def test_calculate_sunrise_sunset(self): @@ -14,11 +11,11 @@ class Test_calculate_sunrise_sunset_normal_case(): time_margin = timedelta(minutes=1) locator = "JN48QM" - test_time = datetime(year=2014, month=1, day=1, tzinfo=UTC) - result_JN48QM_1_1_2014_evening_dawn = datetime(2014, 1, 1, 15, 38, tzinfo=UTC) - result_JN48QM_1_1_2014_morning_dawn = datetime(2014, 1, 1, 6, 36, tzinfo=UTC) - result_JN48QM_1_1_2014_sunrise = datetime(2014, 1, 1, 7, 14, tzinfo=UTC) - result_JN48QM_1_1_2014_sunset = datetime(2014, 1, 1, 16, 15, 23, 31016, tzinfo=UTC) + test_time = datetime(year=2014, month=1, day=1, tzinfo=timezone.utc) + result_JN48QM_1_1_2014_evening_dawn = datetime(2014, 1, 1, 15, 38, tzinfo=timezone.utc) + result_JN48QM_1_1_2014_morning_dawn = datetime(2014, 1, 1, 6, 36, tzinfo=timezone.utc) + result_JN48QM_1_1_2014_sunrise = datetime(2014, 1, 1, 7, 14, tzinfo=timezone.utc) + result_JN48QM_1_1_2014_sunset = datetime(2014, 1, 1, 16, 15, 23, 31016, tzinfo=timezone.utc) assert calculate_sunrise_sunset(locator, test_time)['morning_dawn'] - result_JN48QM_1_1_2014_morning_dawn < time_margin assert calculate_sunrise_sunset(locator, test_time)['evening_dawn'] - result_JN48QM_1_1_2014_evening_dawn < time_margin @@ -33,7 +30,7 @@ class Test_calculate_sunrise_sunset_normal_case(): # The sun never rises in winter time close to the north pole (e.g. at Jan Mayen) # Therefore we expect no sunrise or sunset. - test_time = datetime(year=2021, month=12, day=15, tzinfo=UTC) + test_time = datetime(year=2021, month=12, day=15, tzinfo=timezone.utc) assert calculate_sunrise_sunset(locator, test_time)['morning_dawn'] == None assert calculate_sunrise_sunset(locator, test_time)['evening_dawn'] == None @@ -48,7 +45,7 @@ class Test_calculate_sunrise_sunset_normal_case(): # The sun never sets at the south pole during arctic summer # Therefore we expect no sunrise or sunset. - test_time = datetime(year=2014, month=1, day=1, tzinfo=UTC) + test_time = datetime(year=2014, month=1, day=1, tzinfo=timezone.utc) assert calculate_sunrise_sunset(locator, test_time)['morning_dawn'] == None assert calculate_sunrise_sunset(locator, test_time)['evening_dawn'] == None diff --git a/test/test_lookuplib.py b/test/test_lookuplib.py index f138ce6..bd0f35b 100644 --- a/test/test_lookuplib.py +++ b/test/test_lookuplib.py @@ -1,13 +1,8 @@ -from __future__ import unicode_literals import pytest -import sys from pyhamtools.lookuplib import LookupLib from pyhamtools.exceptions import APIKeyMissingError -if sys.version_info.major == 3: - unicode = str - @pytest.fixture(scope="function", params=[5, -5, "", "foo bar", 11.5, {}, [], None, ("foo", "bar")]) def fixAnyValue(request): return request.param @@ -41,5 +36,5 @@ class TestlookupLibHelper: with pytest.raises(TypeError): fixClublogApi._generate_random_word() - assert type(fixClublogApi._generate_random_word(5)) is unicode + assert type(fixClublogApi._generate_random_word(5)) is str assert len(fixClublogApi._generate_random_word(5)) == 5 diff --git a/test/test_lookuplib_clublogapi.py b/test/test_lookuplib_clublogapi.py index 1af3673..f30bd67 100644 --- a/test/test_lookuplib_clublogapi.py +++ b/test/test_lookuplib_clublogapi.py @@ -1,5 +1,5 @@ import pytest -from datetime import datetime +from datetime import datetime, timezone from pyhamtools.lookuplib import LookupLib @@ -84,7 +84,7 @@ class TestclublogApi_Getters: def test_lookup_callsign(self, fixClublogApi): assert fixClublogApi.lookup_callsign("DH1TW") == response_Exception_DH1TW assert fixClublogApi.lookup_callsign("VU9KV") == response_Exception_VU9KV - d = datetime.utcnow().replace(year=1971, month=4, day=14) + d = datetime.now(timezone.utc).replace(year=1971, month=4, day=14) assert fixClublogApi.lookup_callsign("VU9KV", d) == response_Exception_VU9KV_with_Date assert fixClublogApi.lookup_callsign("DH1TW/MM") == response_Exception_DH1TW_MM assert fixClublogApi.lookup_callsign("DH1TW/AM") == response_Exception_DH1TW_AM diff --git a/test/test_lookuplib_clublogxml.py b/test/test_lookuplib_clublogxml.py index 1adea1b..5241729 100644 --- a/test/test_lookuplib_clublogxml.py +++ b/test/test_lookuplib_clublogxml.py @@ -1,13 +1,10 @@ import pytest -from datetime import datetime -import pytz +from datetime import datetime, timezone import os from pyhamtools.lookuplib import LookupLib from pyhamtools.exceptions import APIKeyMissingError -UTC = pytz.UTC - #Fixtures #=========================================================== @@ -55,8 +52,8 @@ response_Exception_VK9XO_with_start_date = { 'adif': 35, 'country': u'CHRISTMAS ISLAND', 'continent': u'OC', - 'latitude': -10.50, - 'longitude': 105.70, + 'latitude': -10.48, + 'longitude': 105.71, 'cqz': 29, } @@ -142,40 +139,40 @@ class TestclublogXML_Getters: #=============================== def test_lookup_callsign_same_callsign_different_exceptions(self, fixClublogXML): - timestamp = datetime(year=1990, month=10, day=12, tzinfo=UTC) + timestamp = datetime(year=1990, month=10, day=12, tzinfo=timezone.utc) assert fixClublogXML.lookup_callsign("kc6mm", timestamp) == response_Exception_KC6MM_1990 - timestamp = datetime(year=1992, month=3, day=8, tzinfo=UTC) + timestamp = datetime(year=1992, month=3, day=8, tzinfo=timezone.utc) assert fixClublogXML.lookup_callsign("kc6mm", timestamp) == response_Exception_KC6MM_1992 def test_lookup_callsign_exception_only_with_start_date(self, fixClublogXML): #timestamp > startdate - timestamp = datetime(year=1962, month=7, day=7, tzinfo=UTC) + timestamp = datetime(year=1962, month=7, day=7, tzinfo=timezone.utc) assert fixClublogXML.lookup_callsign("vk9xo", timestamp) == response_Exception_VK9XO_with_start_date assert fixClublogXML.lookup_callsign("vk9xo") == response_Exception_VK9XO_with_start_date #timestamp < startdate - timestamp = datetime(year=1962, month=7, day=5, tzinfo=UTC) + timestamp = datetime(year=1962, month=7, day=5, tzinfo=timezone.utc) with pytest.raises(KeyError): fixClublogXML.lookup_callsign("vk9xo", timestamp) def test_lookup_callsign_exception_only_with_end_date(self, fixClublogXML): #timestamp < enddate - timestamp = datetime(year=1975, month=9, day=14, tzinfo=UTC) + timestamp = datetime(year=1975, month=9, day=14, tzinfo=timezone.utc) assert fixClublogXML.lookup_callsign("vk9xx", timestamp) == response_Exception_VK9XX_with_end_date # timestamp > enddate with pytest.raises(KeyError): fixClublogXML.lookup_callsign("vk9xx") - timestamp = datetime(year=1975, month=9, day=16, tzinfo=UTC) + timestamp = datetime(year=1975, month=9, day=16, tzinfo=timezone.utc) with pytest.raises(KeyError): fixClublogXML.lookup_callsign("vk9xx", timestamp) def test_lookup_callsign_exception_no_start_nor_end_date(self, fixClublogXML): - timestamp = datetime(year=1975, month=9, day=14, tzinfo=UTC) + timestamp = datetime(year=1975, month=9, day=14, tzinfo=timezone.utc) assert fixClublogXML.lookup_callsign("ax9nyg", timestamp) == response_Exception_AX9NYG assert fixClublogXML.lookup_callsign("ax9nyg" ) == response_Exception_AX9NYG @@ -196,29 +193,29 @@ class TestclublogXML_Getters: def test_lookup_prefix_with_changing_entities(self, fixClublogXML): #return old entity (PAPUA TERR) - timestamp = datetime(year=1975, month=9, day=14).replace(tzinfo=UTC) + timestamp = datetime(year=1975, month=9, day=14, tzinfo=timezone.utc) assert fixClublogXML.lookup_prefix("VK9", timestamp) == response_Prefix_VK9_until_1975 #return empty dict - Prefix was not assigned at that time - timestamp = datetime(year=1975, month=9, day=16).replace(tzinfo=UTC) + timestamp = datetime(year=1975, month=9, day=16, tzinfo=timezone.utc) with pytest.raises(KeyError): fixClublogXML.lookup_prefix("VK9", timestamp) #return new entity (Norfolk Island) - timestamp = datetime.utcnow().replace(tzinfo=UTC) + timestamp = datetime.now(timezone.utc) assert fixClublogXML.lookup_prefix("VK9", timestamp ) == response_Prefix_VK9_starting_1976 def test_lookup_prefix_with_entities_having_start_and_stop(self, fixClublogXML): - timestamp_before = datetime(year=1964, month=11, day=1).replace(tzinfo=UTC) + timestamp_before = datetime(year=1964, month=11, day=1, tzinfo=timezone.utc) with pytest.raises(KeyError): fixClublogXML.lookup_prefix("ZD5", timestamp_before) - timestamp_valid = datetime(year=1964, month=12, day=2).replace(tzinfo=UTC) + timestamp_valid = datetime(year=1964, month=12, day=2, tzinfo=timezone.utc) assert fixClublogXML.lookup_prefix("ZD5", timestamp_valid) == response_Prefix_ZD5_1964_to_1971 - timestamp_after = datetime(year=1971, month=8, day=1).replace(tzinfo=UTC) + timestamp_after = datetime(year=1971, month=8, day=1, tzinfo=timezone.utc) with pytest.raises(KeyError): fixClublogXML.lookup_prefix("ZD5", timestamp_after) @@ -234,8 +231,8 @@ class TestclublogXML_Getters: fixClublogXML.is_invalid_operation("dh1tw") #Invalid Operation with start and end date - timestamp_before = datetime(year=1993, month=12, day=30).replace(tzinfo=UTC) - timestamp = datetime(year=1994, month=12, day=30).replace(tzinfo=UTC) + timestamp_before = datetime(year=1993, month=12, day=30, tzinfo=timezone.utc) + timestamp = datetime(year=1994, month=12, day=30, tzinfo=timezone.utc) with pytest.raises(KeyError): fixClublogXML.is_invalid_operation("vk0mc") @@ -246,7 +243,7 @@ class TestclublogXML_Getters: #Invalid Operation with start date assert fixClublogXML.is_invalid_operation("5W1CFN") - timestamp_before = datetime(year=2012, month=1, day=31).replace(tzinfo=UTC) + timestamp_before = datetime(year=2012, month=1, day=31, tzinfo=timezone.utc) with pytest.raises(KeyError): fixClublogXML.is_invalid_operation("5W1CFN", timestamp_before) @@ -264,9 +261,9 @@ class TestclublogXML_Getters: assert fixClublogXML.lookup_zone_exception("dp0gvn") == 38 #zone exception with start and end date - timestamp = datetime(year=1992, month=10, day=2).replace(tzinfo=UTC) - timestamp_before = datetime(year=1992, month=9, day=30).replace(tzinfo=UTC) - timestamp_after = datetime(year=1993, month=3, day=1).replace(tzinfo=UTC) + timestamp = datetime(year=1992, month=10, day=2, tzinfo=timezone.utc) + timestamp_before = datetime(year=1992, month=9, day=30, tzinfo=timezone.utc) + timestamp_after = datetime(year=1993, month=3, day=1, tzinfo=timezone.utc) assert fixClublogXML.lookup_zone_exception("dl1kvc/p", timestamp) == 38 with pytest.raises(KeyError): @@ -276,6 +273,6 @@ class TestclublogXML_Getters: fixClublogXML.lookup_zone_exception("dl1kvc/p", timestamp_after) #zone exception with start date - timestamp_before = datetime(year=2013, month=12, day=26).replace(tzinfo=UTC) + timestamp_before = datetime(year=2013, month=12, day=26,tzinfo=timezone.utc) with pytest.raises(KeyError): fixClublogXML.lookup_zone_exception("dh1hb/p", timestamp_before) diff --git a/test/test_lookuplib_gettersetter_api.py b/test/test_lookuplib_gettersetter_api.py index 8274f17..7b23aae 100644 --- a/test/test_lookuplib_gettersetter_api.py +++ b/test/test_lookuplib_gettersetter_api.py @@ -1,11 +1,4 @@ -from __future__ import unicode_literals import pytest -import tempfile -import os -import sys - -if sys.version_info.major == 3: - unicode = str from datetime import datetime @@ -58,13 +51,13 @@ class Test_Getter_Setter_Api_Types_for_all_sources: count = 0 for attr in entity: if attr == "country": - assert type(entity[attr] is unicode) + assert type(entity[attr] is str) count +=1 if attr == "continent": - assert type(entity[attr] is unicode) + assert type(entity[attr] is str) count +=1 if attr == "prefix": - assert type(entity[attr] is unicode) + assert type(entity[attr] is str) count +=1 if attr == "deleted": assert type(entity[attr] is bool) @@ -114,10 +107,10 @@ class Test_Getter_Setter_Api_Types_for_all_sources: assert type(ex[attr]) is float count +=1 elif attr == "country": - assert type(ex[attr]) is unicode + assert type(ex[attr]) is str count +=1 elif attr == "continent": - assert type(ex[attr]) is unicode + assert type(ex[attr]) is str count +=1 elif attr == "cqz": assert type(ex[attr]) is int @@ -150,7 +143,7 @@ class Test_Getter_Setter_Api_Types_for_all_sources: count = 0 for attr in prefix: if attr == "country": - assert type(prefix[attr]) is unicode + assert type(prefix[attr]) is str count +=1 elif attr == "adif": assert type(prefix[attr]) is int @@ -162,7 +155,7 @@ class Test_Getter_Setter_Api_Types_for_all_sources: assert type(prefix[attr]) is int count +=1 elif attr == "continent": - assert type(prefix[attr]) is unicode + assert type(prefix[attr]) is str count +=1 elif attr == "latitude": assert type(prefix[attr]) is float diff --git a/test/test_lookuplib_qrz.py b/test/test_lookuplib_qrz.py index 8a4476e..b63cc4b 100644 --- a/test/test_lookuplib_qrz.py +++ b/test/test_lookuplib_qrz.py @@ -1,16 +1,12 @@ import os import pytest -from datetime import datetime +from datetime import datetime, timezone from pyhamtools.lookuplib import LookupLib from pyhamtools.exceptions import APIKeyMissingError from pyhamtools.consts import LookupConventions as const -import pytz -UTC = pytz.UTC - - try: QRZ_USERNAME = str(os.environ['QRZ_USERNAME']) QRZ_PWD = str(os.environ['QRZ_PWD']) @@ -21,10 +17,10 @@ except Exception: #=========================================================== response_1A1AB = { - u'biodate': datetime(2018, 9, 7, 21, 17, 7, tzinfo=UTC), + u'biodate': datetime(2018, 9, 7, 21, 17, 7, tzinfo=timezone.utc), u'bio': u'0', u'license_class': u'C', - u'moddate': datetime(2008, 11, 2, 15, 0, 38, tzinfo=UTC), + u'moddate': datetime(2008, 11, 2, 15, 0, 38, tzinfo=timezone.utc), u'locator': u'JN61fw', u'callsign': u'1A1AB', u'addr2': u'00187 Rome', diff --git a/test/test_lookuplib_redis.py b/test/test_lookuplib_redis.py index 2ccb0dc..b244c3c 100644 --- a/test/test_lookuplib_redis.py +++ b/test/test_lookuplib_redis.py @@ -1,15 +1,11 @@ import pytest import json -from datetime import datetime +from datetime import datetime, timezone -import pytz import redis from pyhamtools import LookupLib, Callinfo - -UTC = pytz.UTC - r = redis.Redis() @@ -44,7 +40,7 @@ class TestStoreDataInRedis: with pytest.raises(KeyError): fix_redis.is_invalid_operation("VK0MC") - timestamp = datetime(year=1994, month=12, day=30).replace(tzinfo=UTC) + timestamp = datetime(year=1994, month=12, day=30, tzinfo=timezone.utc) assert fix_redis.is_invalid_operation("VK0MC", timestamp) with pytest.raises(KeyError): @@ -61,7 +57,7 @@ class TestStoreDataInRedis: assert lib.lookup_prefix("DH") == fixCountryFile.lookup_prefix("DH") def test_redis_lookup(self, fixClublogXML, fix_redis): - timestamp = datetime(year=2016, month=1, day=20, tzinfo=UTC) + timestamp = datetime(year=2016, month=1, day=20, tzinfo=timezone.utc) ci = Callinfo(fix_redis) assert ci.get_all("VP8STI", timestamp) == response_Exception_VP8STI_with_start_and_stop_date assert ci.get_all("tu5pct") == response_TU5PCT diff --git a/test/test_lotw.py b/test/test_lotw.py index 7ea4e53..afd676c 100644 --- a/test/test_lotw.py +++ b/test/test_lotw.py @@ -1,9 +1,7 @@ import os -import sys import datetime from .execfile import execfile -from future.utils import iteritems import pytest def execfile(filepath, globals=None, locals=None): @@ -19,9 +17,6 @@ def execfile(filepath, globals=None, locals=None): from pyhamtools.qsl import get_lotw_users -if sys.version_info.major == 3: - unicode = str - test_dir = os.path.dirname(os.path.abspath(__file__)) fix_dir = os.path.join(test_dir, 'fixtures') @@ -38,8 +33,8 @@ class Test_lotw_methods: data = get_lotw_users() assert isinstance(data, dict) - for key, value in iteritems(data): - assert isinstance(key, unicode) + for key, value in data.items(): + assert isinstance(key, str) assert isinstance(value, datetime.datetime ) assert len(data) > 1000